Tuesday, February 5, 2019 by Nate
SPAs, or single page applications, are all the rage these days. They have their merits, and they are beneficial for many scenarios. One issue that has plagued me repeatedly when working on SPAs is trying to define environment specific variables that are NOT KNOWN at build time. WebPack is great, and scary, and confusing all at the same time, but it packages everything up at once. If you don’t know the value at build time, you’re out of luck.
While there are dozens of ways to handle this situation, some including separate build and release pipelines with tokens, I opted for a more low tech solution. Let me set the stage before I continue, as I think it helps paint the picture around why I like this solution. My app connects to some web services, and the uri of said services will be different for each deployment. Its important to note, that my SPA and web services are hosted on different domains and setup with CORS configuration. I can’t simply use relative paths.
I created a simple config.js file and included it in the head section of my index.html (the entry point for my SPA). It looks like this:
window.api_root_url = "https://runtime-api.example.com";
window.client_id = "my-client-id-for-open-id-connect";
In my SPA code (I’m using typescript) I created a simple globals.ts file which wraps these into type safe constants.
export const apiRootUrl: string = (window as any).api_root_url;
export const clientId: string = (window as any).client_id;
and then whenever I need to reference my api endpoint or my client id I can simply import and use:
import * as globals from '@/globals';
// later on...
console.log(globals.apiRootUrl);
This works well, and provides a single file to edit on the deployed solution. It makes it easy to configure via ftp, ssh, rdp, etc.
Monday, January 21, 2019 by Nate Bross
Someone asked on Stack Overflow:
I would like to make a Identity server and the API in two separate dockers for a native application (client mobile). It is running on an NGINX Reverse Proxy and Let’s Encrypt.
Dockers
---------------------------
| Reverse Proxy |
| ----------------------- |
-------- | | ---------------- | |
| Mobile | ---------|-> | IdendityServer | | |
-------- | | | Port: 5000 | | |
| | | ---------------- | |
| | | | | |
| | | ---------------- | |
---------------|-> | API | | |
| | | Port: 5001 | | |
| | ---------------- | |
| ----------------------- |
| |
| ---------------- |
| | PostgreSQL | |
| | Port: 5432 | |
| ---------------- |
---------------------------
With my current configuration:
- The Reverse Proxy with Let’s Encrypt is well working from the mobile
- The call API without [Authority] is well working from the mobile
- The Identity server connection with the Hybrid flow is working and my user’s claims is listed
My codes are below.
IdentityServer Dockerfile
FROM microsoft/dotnet:2.0-sdk
COPY is4/* /app/
WORKDIR /app
ENV ASPNETCORE_URLS http://*:5000
EXPOSE 5000
ENTRYPOINT ["dotnet", "IdentityServer.dll"]
API Dockerfile
FROM microsoft/dotnet:2.0-sdk
COPY api/* /app/
WORKDIR /app
ENV ASPNETCORE_URLS http://*:5001
EXPOSE 5001
ENTRYPOINT ["dotnet", "ApiServer.dll"]
DockerCompose
version: '3'
services:
identityserver:
image: identityserver
build:
context: .
dockerfile: IdentityServer/Dockerfile
container_name: ids
restart: always
ports:
- 5000:5000
# expose:
# - "5000"
environment:
ASPNETCORE_ENVIRONMENT: Development
VIRTUAL_PORT: 5000
VIRTUAL_HOST: ids.mydomain.com
LETSENCRYPT_HOST: ids.mydomain.com
LETSENCRYPT_EMAIL: myuser@mydomain.com
IDENTITY_ISSUER: "https://ids.mydomain.com"
IDENTITY_REDIRECT: "com.mobiletest.nativeapp"
IDENTITY_CORS_ORIGINS: "https://ids.mydomain.com"
depends_on:
- db
apiserver:
image: apiserver
build:
context: .
dockerfile: ApiServer/Dockerfile
container_name: api
restart: always
ports:
- 5001:5001
# expose:
# - "5001"
environment:
ASPNETCORE_ENVIRONMENT: Development
VIRTUAL_PORT: 5001
VIRTUAL_HOST: api.mydomain.com
LETSENCRYPT_HOST: api.mydomain.com
LETSENCRYPT_EMAIL: myuser@mydomain.com
IDENTITY_AUTHORITY: "http://identityserver:5000"
CLIENT_CORS_ORIGINS: "com.mobiletest.nativeapp"
depends_on:
- identityserver
- db
links:
- identityserver
db:
image: postgresql:10
build:
context: .
dockerfile: PostgreSQL/Dockerfile
container_name: db
restart: always
ports:
- "5432:5432"
volumes:
- /www/database:/var/lib/postgresql/data
environment:
- PGDATA=/var/lib/postgresql/data/pgdata
networks:
default:
external:
name: nginx-proxy
IdentityServer Startup Code
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
// Configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer(opt =>
{
opt.IssuerUri = Configuration["IDENTITY_ISSUER"];
opt.PublicOrigin = Configuration["IDENTITY_ISSUER"];
})
.AddCorsPolicyService<InMemoryCorsPolicyService>() // Add the CORS service
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<ApplicationUser>();
services.AddAuthentication();
// preserve OIDC state in cache (solves problems with AAD and URL lenghts)
services.AddOidcStateDataFormatterCache("aad");
// add CORS policy for non-IdentityServer endpoints
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", policy =>
{
policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
});
});
} // ConfigureServices()
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseIdentityServer();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
} // Configure()
IdentityServer Config Code
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api1", "My API")
{
ApiSecrets = { new Secret("secret".Sha256()) }
}
};
}
public static IEnumerable<Client> GetClients()
{
// client credentials client
return new List<Client>
{
new Client
{
ClientId = "native.hybrid",
ClientName = "Native Client (Hybrid with PKCE)",
AllowedGrantTypes = GrantTypes.Hybrid,
RequirePkce = true,
RequireConsent = false,
//RequireClientSecret = false,
ClientSecrets = { new Secret("secret".Sha256()) },
RedirectUris = { Configuration["IDENTITY_REDIRECT"] + "://signin-oidc" },
PostLogoutRedirectUris = { Configuration["IDENTITY_REDIRECT"] + "://signout-callback-oidc" },
AllowedScopes = { "openid", "profile" },
AllowedCorsOrigins = { Configuration["IDENTITY_CORS_ORIGINS"] },
AllowOfflineAccess = true,
//AllowAccessTokensViaBrowser = true
RefreshTokenUsage = TokenUsage.ReUse
}
};
} // GetClients()
Api Config code
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
if (Configuration["CLIENT_CORS_ORIGINS"] == "")
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyMethod()
.AllowAnyOrigin()
.AllowAnyHeader());
});
}
else
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyHeader()
.AllowAnyMethod()
.WithOrigins(Configuration["CLIENT_CORS_ORIGINS"]));
});
}
services.AddAuthentication("Bearer");
services.AddAuthentication(options => //adds the authentication services to DI
{
//We are using a cookie as the primary means to authenticate a user (via “Cookies” as the DefaultScheme). We set the DefaultChallengeScheme to “oidc” because when we need the user to login, we will be using the OpenID Connect scheme.
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies") //add the handler that can process cookies
.AddOpenIdConnect("oidc", options => //configure the handler that perform the OpenID Connect protocol
{
options.SignInScheme = "Cookies"; //is used to issue a cookie using the cookie handler once the OpenID Connect protocol is complete
options.Authority = Configuration["IDENTITY_AUTHORITY"]; //indicates that we are trusting IdentityServer
options.RequireHttpsMetadata = false;
options.ClientId = "native.hybrid";
options.SaveTokens = true;
options.ClientSecret = "secret"; //used to persist the tokens from IdentityServer in the cookie
options.ResponseType = "code id_token";
});
services.AddMvc();
} // ConfigureServices()
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
app.UseCors("CorsPolicy");
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseMvc();
} // Configure()
API Controller code
[Route("api/[controller]")]
[EnableCors("CorsPolicy")]
[Authorize]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "testvalue1", "testvalue2" };
}
}
On Xamarin for the client mobile
var options = new OidcClientOptions
{
Authority = "https://ids4.syladebox.com",
ClientId = "native.hybrid",
ClientSecret = "secret",
//Scope = "openid profile api1 offline_access",
Scope = "openid profile offline_access",
ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect,
RedirectUri = "com.mobiletest.nativeapp://signin-oidc",
PostLogoutRedirectUri = "com.mobiletest.nativeapp://signout-callback-oidc",
//Flow = OidcClientOptions.AuthenticationFlow.Hybrid,
//Policy = policy,
//Browser = new SFAuthenticationSessionBrowser()
// new in iOS 12
Browser = new ASWebAuthenticationSessionBrowser()
//Browser = new PlatformWebView()
};
_client = new OidcClient(options);
var result = await _client.LoginAsync(new LoginRequest());
if (result.IsError)
{
OutputText.Text = result.Error;
return;
}
if (result.AccessToken != null)
{
var client = new HttpClient();
client.SetBearerToken(result.AccessToken);
var response = await client.GetAsync("https://api.mydomain.com/api/values");
if (!response.IsSuccessStatusCode)
{
OutputText.Text = response.ReasonPhrase;
return;
}
var content = await response.Content.ReadAsStringAsync();
OutputText.Text = JArray.Parse(content).ToString();
}
Problem not resolved:
The problem is to unable to invoke the API on behalf of my user. It returns either or “No authorized” either or “Bad gateway” after:
response = await client.GetAsync("https://api.mydomain.com/api/values");
These errors are depending of the environment variables in the IdentityServer and Api dockers.
My current environment variables are:
IDENTITY_ISSUER: “https://ids.mydomain.com”
IDENTITY_REDIRECT: “com.mobiletest.nativeapp”
IDENTITY_CORS_ORIGINS: “https://ids.mydomain.com”
IDENTITY_AUTHORITY: “http://identityserver:5000”
CLIENT_CORS_ORIGINS: “com.mobiletest.nativeapp”
The API call (https://api.mydomain.com/api/values) returns “Bad gateway”.
I think that IDENTITY_ISSUER, IDENTITY_REDIRECT are corrects because the Identity server connexion is successful.
Is the problem coming from the other environment variables (IDENTITY_CORS_ORIGINS, IDENTITY_AUTHORITY and CLIENT_CORS_ORIGINS) or the Identity Server/API codes?
Update January, 26:
To make sure if my API program works, I redid the API program to the simplest:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Authority = Configuration["IDENTITY_AUTHORITY"];
options.ApiName = "api";
//options.ApiSecret = "secret";
});
// Add CORS policy for non-IdentityServer endpoints
services.AddCors(options =>
{
options.AddPolicy("api", policy =>
{
policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
});
});
} // ConfigureServices()
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
app.UseCors("api");
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseMvc();
} // Configure()
with the api controller:
[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme)]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "testvalue1", "testvalue2" };
}
}
and I use in my first test the other identity server :
demo.identityserver.io
For this test, I make the following configuration:
apiserver:
...
ports:
- 5001:80
environment:
...
IDENTITY_AUTHORITY: "https://demo.identityserver.io"
#CLIENT_CORS_ORIGINS (omitted in the code)
My OidcClientOptions in the client code is:
var options = new OidcClientOptions
{
Authority = "https://demo.identityserver.io",
ClientId = "native.hybrid",
Scope = "openid profile email api offline_access",
ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect,
RedirectUri = "com.mobiletest.nativeapp://callback",
PostLogoutRedirectUri = "com.mobiletest.nativeapp://callback",
Browser = new ASWebAuthenticationSessionBrowser()
};
The login function is in my first topic.
- The Identity server connection with the Hybrid flow is working
- The API call is successful!
As demo.identityserver.io is the demonstration identity server, I have a doubt if it works as the case of production then I tested an other identity server (Okta) with the same API program:
dev-xxxxxx.okta.com
For this test, I make the following configuration:
apiserver:
...
ports:
- 5001:80
environment:
...
IDENTITY_AUTHORITY: "https://dev-xxxxxx.okta.com"
#CLIENT_CORS_ORIGINS (omitted in the code)
My OidcClientOptions in the client code is:
var options = new OidcClientOptions
{
Authority = "https://dev-xxxx.okta.com",
ClientId = "xxxxxxxxxxxxxxxxxxx", // ClientId is hidden in this topic
Scope = "openid profile email offline_access",
ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect,
RedirectUri = "com.okta.dev-xxxxxx:/callback",
PostLogoutRedirectUri = "com.okta.dev-xxxxxx:/callback",
Browser = new ASWebAuthenticationSessionBrowser()
};
- The Identity server connection with the Hybrid flow is working
- The API call isn’t working, it returns the message “Unauthorized”.
With the two tests, I can not tell if my API program works well.
Could you help me? Thank you very much!
I posted the following answer, which was chosen as the accepted answer:
Have you tried using .AddJwtBearer() in your API instead of just .AddAuthentication("Bearer")?
Something like this:
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.Audience = "api1";
});
Source: http://docs.identityserver.io/en/latest/quickstarts/1_client_credentials.html
While the source is referring to client credentials, I think you probably want to be using Jwt Tokens regardless of the client type.
Originally posted on Stack Overflow — 0 upvotes (accepted answer). Licensed under CC BY-SA.
Thursday, December 27, 2018 by Nate Bross
There are a few data visualization projects I want to tackle and a good number of them have a cartography component. I knew I had to get started with one and build on it or I’d never get any of them rolling. I ran across a ‘Functional System Lane Length’ data set from Federal Highway Administration. I figured the table could be cleaned up and presented in a more intuitive way using a map and it would force me to kick start this little prototype.
This is what I came up with, its still a work in progress, though updates will likely come slowly and will be applied to the next data visualization project. This is how I approached it.
Visualization Technology
I had worked with jqvmap on some previous projects, and they have a great sample for color coding a map based on a set of data, so I decided to stick with something I already knew. I also knew I wanted to throw this in a separate page that I could embed on my blog here, so I wanted to make sure it could be completely self contained.
Data Processing
The data was available in an html table form, as well as pdf and excel. I found an online html table to json converter, and used that to build an array of data from the table. This got me close to what I wanted, but it left a bad taste in my mouth indexing into a multi dimensional array to pull out data every time I wanted to change data sets. This is what it looked like before any conversion:
[
["INTERSTATE","OTHER FREEWAYS AND EXPRESSWAYS","OTHER PRINCIPAL ARTERIAL","MINOR ARTERIAL","MAJOR COLLECTOR","MINOR COLLECTOR (2)","LOCAL (2)","TOTAL","INTERSTATE","OTHER FREEWAYS AND EXPRESSWAYS","OTHER PRINCIPAL ARTERIAL","MINOR ARTERIAL","MAJOR COLLECTOR","MINOR COLLECTOR","LOCAL (2)","TOTAL","","" ],
["Alabama","2,414","-","6,045","8,398","24,615","12,441","94,725","148,638","2,189","138","4,905","6,070","7,547","372","41,481","62,701","211,339" ],
["Alaska","2,057","-","1,612","867","2,743","2,862","15,294","25,434","303","-","503","475","510","472","3,898","6,162","31,597" ],
["Arizona","3,714","70","3,417","2,691","8,552","3,789","61,350","83,584","1,462","1,552","3,771","9,278","4,352","456","40,505","61,375","144,959" ],
["Arkansas","1,752","288","4,996","6,360","23,780","13,656","122,548","173,378","1,469","424","2,208","4,364","4,504","499","23,685","37,154","210,532" ],
["California","5,254","1,518","8,253","12,647","24,037","15,080","82,233","149,022","9,671","9,283","25,149","30,758","27,593","644","142,263","245,361","394,383" ],
]
Clearly, something needed done, so I wrote a small processing utility and JSON.stringify’d it:
var ddt = [];
for (var i = 0; i < states.length; i++) {
var abvr = StateAbvrFromName(states[i][0]); // since data shows state name and jqvmap uses state code, il, ca, etc
ddt[ddt.length] = {
'State': { 'Name' : states[i][0], 'Code': abvr },
'Rural': {
'Interstate': parseInt(states[i][1].replace(/,/g, ""), 10),
'Other_Freeways_Expressways': parseInt(states[i][2].replace(/,/g, ""), 10),
'Other_Principal_Arterial': parseInt(states[i][3].replace(/,/g, ""), 10),
'Minor_Arterial': parseInt(states[i][4].replace(/,/g, ""), 10),
'Major_Collector':parseInt(states[i][5].replace(/,/g, ""), 10),
'Minor_Collector_2': parseInt(states[i][6].replace(/,/g, ""), 10),
'Local_2': parseInt(states[i][7].replace(/,/g, ""), 10),
'Total': parseInt(states[i][8].replace(/,/g, ""), 10),
},
'Urban': {
'Interstate': parseInt(states[i][9].replace(/,/g, ""), 10),
'Other_Freeways_Expressways': parseInt(states[i][10].replace(/,/g, ""), 10),
'Other_Principal_Arterial': parseInt(states[i][11].replace(/,/g, ""), 10),
'Minor_Arterial': parseInt(states[i][12].replace(/,/g, ""), 10),
'Major_Collector': parseInt(states[i][13].replace(/,/g, ""), 10),
'Minor_Collector_2': parseInt(states[i][14].replace(/,/g, ""), 10),
'Local_2': parseInt(states[i][15].replace(/,/g, ""), 10),
'Total': parseInt(states[i][16].replace(/,/g, ""), 10),
},
'Total' : parseInt(states[i][17].replace(/,/g, ""), 10),
};
}
Throwing It All Together
Going in to this little project, I knew that I wanted to run without any server side code, I wanted users to be able to minipulate the data to change the map, and I knew I wanted it to be as light weight as possible. I didn’t want any node modules or components or any ‘build’ time tooling to be required to make this work.
I opted to use some jQuery event handling to glue everything together, and I’m actually happy with how it worked out.
Thursday, December 27, 2018 by Nate Bross
Someone asked on Stack Overflow:
Update: After correcting the certificate issue, I’m now getting a 500 response form the test, with this message:
InvalidOperationException: IDX20803: Unable to obtain configuration from: ‘http://localhost/.well-known/openid-configuration’.
That appears to be similar to this issue: https://github.com/IdentityServer/IdentityServer4/issues/685; however, I can’t come up with a way to set the backchannel client or handler from my test – it seems like a chicken and egg situation.
This issue was fixed by using a real certificate/.pfx file. That lead to the above issue.
I’m using WebApplicationFactory to do integration tests over my API, and I think I’ve covered all the bases with getting the http clients configured correctly. I’m getting an error when calling an action in my API.
This is the error when executing a get against the api with a token:
WWW-Authenticate: Bearer error=“invalid_token”, error_description=“The signature key was not found”
Here’s a simple test class that demonstrates this problem:
public class EntityControllerShould : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
public EntityControllerShould(WebApplicationFactory<Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task ReturnListOfEntities_ForGet()
{
// arrange
_factory.CreateClient();
var handler = _factory.Server.CreateHandler();
var client = new HttpClient(handler) { BaseAddress = new System.Uri("http://localhost/") };
// discover endpoints from metadata
var dc = new DiscoveryClient(client.BaseAddress.ToString(), handler);
var disco = await dc.GetAsync();
if (disco.IsError)
{
Assert.True(false);
}
// request token
var tokenClient = new TokenClient(disco.TokenEndpoint, "api_client", "secret", handler);
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");
if (tokenResponse.IsError)
{
Assert.True(false);
}
client.SetBearerToken(tokenResponse.AccessToken);
// act
var response = await client.GetAsync("api/entity/?id=123");
// response code is 401 with the above quoted error in the header
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
// assert
Assert.NotNull(responseString);
}
}
Snip from Startup.cs in the API project:
services.AddIdentityServer()
.AddSigningCredential(myCertificate)
.AddSigningCredential()
.AddClientStore<CustomClientStore>()
.AddInMemoryIdentityResources(IdentityServerConfig.IdentityResources)
.AddInMemoryApiResources(IdentityServerConfig.Apis);
- I am hosting the IdentityServer4 and the API in the same project.
- When I manually perform integration tests, through a browser, it works just fine
- I have found this which seems very similar
Is there something I need to account for when running in the context of an xunit test that I am not?
I posted the following answer, which was chosen as the accepted answer and received 1 upvote:
The only solution I’ve come up with, is to setup a static Handler on the Startup class of the API project and then override it from within each unit test.
Specifically:
// startup.cs
public static HttpMessageHandler Handler { get; set; }
// snip from configureservices
.AddJwtBearer(jwt =>
{
// defaults as they were
jwt.Authority = "http://localhost:5000/";
jwt.RequireHttpsMetadata = false;
jwt.Audience = "api1";
// if static handler is null don't change anything, otherwise assume integration test.
if(Handler == null)
{
jwt.BackchannelHttpHandler = Handler;
jwt.Authority = "http://localhost/";
}
});
Complete listing from IdSvrAndApi project:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public static HttpMessageHandler Handler { get; set; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryClients(Config.Clients)
.AddInMemoryApiResources(Config.Apis)
.AddTestUsers(TestUsers.Users);
services.AddAuthentication()
.AddJwtBearer(jwt =>
{
jwt.BackchannelHttpHandler = Handler;
jwt.Authority = "http://localhost/";
jwt.RequireHttpsMetadata = false;
jwt.Audience = "api1";
});
.AddJwtBearer(jwt =>
{
// defaults as they were
jwt.Authority = "http://localhost:5000/";
jwt.RequireHttpsMetadata = false;
jwt.Audience = "api1";
// if static handler is null don't change anything, otherwise assume integration test.
if(Handler == null)
{
jwt.BackchannelHttpHandler = Handler;
jwt.Authority = "http://localhost/";
}
});
}
A fully working sample of this can be seen here: https://github.com/fuzzzerd/IdentityServerAndApi.
I have not received a definitive answer on if this is the recommended way to acheive this, but I did open an issue on the project here: https://github.com/IdentityServer/IdentityServer4/issues/2877
Originally posted on Stack Overflow — 1 upvotes (accepted answer). Licensed under CC BY-SA.
Saturday, November 17, 2018 by Nate Bross
The ingredients:
- 2 cups flour
- 2 tsp baking powder
- 1 tsp salt
- 1/2 tsp baking soda
- 3 Tbs sugar
- 2 eggs, separated
- 2 cups buttermilk (or more)
- 1/4 cup melted butter
Sift flour, measure and re-sift with other dry ingredients. Combine beaten yolks with buttermilk and add to dry ingredients. Add melted butter. Beat egg whites until stiff and fold into mixture. Bake on hot waffle iron or make into pancakes.
Saturday, November 17, 2018 by Nate Bross
I’m using IdentityServer4 for a couple projects. Its great, and might warrant a post of its own; however, one thing I’ve been struggling with is loading the jwt signing certificates. I’ve been using the powershell cmdlet New-SelfSignedCertificate to generate the pfx files and it works great on my local computer.
Running this on a web server has caused some grief, but lead to my learning a bit more about how these things actually work.
This is the final code that works, but I’ll walk through how I got to it.
new X509Certificate2(keyFilePath, keyFilePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.EphemeralKeySet)
The first thing is that the certificate file itself can have indicators stating whether to load to User store or Machine store; so I’ve used MachineKeySet to ensure we override that.
Additionally I’m also specifying EphemeralKeySet, which indicates the private keys should not be persisted anywhere. This is the key part that allows everything to work when running as ApplicationPoolIdentity on a server without administrative privileges.
Without passing the additional Storage Flags, the system uses whats in the certificate file/data and that works locally since I have a user profile and/or administrative rights. By default, IIS does NOT load the user profile which means no environment variables or user certificate store. In order to use MachineKeySet you need administrative privileges (something your web facing accounts should not have) so that’s where Ephemeral comes in to play. Nothing is persisted so the admin rights are not needed. There are some incompatibilities with this approach; noted in the sources below, but for jwt signing it works.
For reference and searches, here are the errors I got/overcame.
Using default constructor without flags:
Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: The system cannot find the file specified
Specifying only MachineKeySet, while running as ApplicationPoolIdentity:
Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: Access denied
Relevant sources:
Thursday, October 18, 2018 by Nate Bross
Someone asked on Stack Overflow:
In my Angular 5 application, I would like to save an “event” object with the following method :
save(newEvent: NewEvent): Observable<any> {
const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) };
const requestUrl = 'api/event';
return this.http.post<any>(requestUrl, newEvent, httpOptions);
}
The NewEvent model is :
export class NewEvent {
id: number;
title: string;...
constructor() {
this.id = null;
this.title = '';...
}
}
And after I call this with a method in a controller with .NET Core :
[HttpPost("api/event")]
public IDictionary<string, Object> SaveEvent([FromBody] EventViewModel model){...}
The EventViewModel POCO is :
{
public class EventViewModel
{
public int Id { get; set; }
public string Title { get; set; }...
}
}
When my object EventViewModel has no attributes with a “null” value it works with no problems, but when I have “null” attributes it doesn’t work (the EventViewModel became “null”) and I want to have sometimes “null” attributes.
I posted the following answer, which was chosen as the accepted answer and received 2 upvotes:
Your C# model has a non-nullable int for the Id column. If you want it to be nullable (as you set it to null in your javascript model) you should define it as int? or Nullable<int>.
See also: Nullable Types.
Originally posted on Stack Overflow — 2 upvotes (accepted answer). Licensed under CC BY-SA.
Thursday, October 18, 2018 by Nate Bross
Someone asked on Stack Overflow:
I ran the example given at the web site under Visual Studio 2017 Community Edition and it worked fine. However, when I attempted to run it on a winforms library, it hung, even when given the exact same term:
Console Version
static void Main(string[] args)
{
var newsApiClient = new NewsApiClient("KeyRedacted");
var articlesResponse = newsApiClient.GetEverything(new EverythingRequest
{
Q = "Apple",
SortBy = SortBys.Popularity,
Language = Languages.EN,
From = new DateTime(2018, 10, 16)
});
if (articlesResponse.Status == Statuses.Ok)
{
//code here
Winforms Version
private void btnSearch_Click(object sender, EventArgs e)
{
var newsApiClient = new NewsApiClient("keyredacted");
var articleResponse = newsApiClient.GetEverything(new EverythingRequest
{
Q = "Apple",
SortBy = SortBys.Popularity,
Language = Languages.EN,
From = new DateTime(2018, 10, 16)
}); //this is where it hangs
if (articleResponse.Status == Statuses.Ok)
{
I posted the following answer, which was chosen as the accepted answer and received 1 upvote:
Assuming that you’re using this client, the method you’re calling uses a Task.Result which can cause a deadlock. Seems reasonable, since the code/signature and examples match.
I would rewrite your code like this for WinForms:
private async void btnSearch_Click(object sender, EventArgs e)
{
var newsApiClient = new NewsApiClient("keyredacted");
var articleResponse = await newsApiClient.GetEverythingAsync(new EverythingRequest
...
If you are deadset on not using the async methods in your code you could try running it inside of a Task.Run(() => /* stuff */);
See also: https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/
Originally posted on Stack Overflow — 1 upvotes (accepted answer). Licensed under CC BY-SA.
Friday, September 28, 2018 by Nate Bross
Someone asked on Stack Overflow:
I don’t quite get the difference between UseHsts and UseHttpsRedirection in the configure section of the startup file in .net core. Could anyone explain?
I posted the following answer, which was chosen as the accepted answer and received 49 upvotes:
According to the documentation you should use both together:
We recommend all production ASP.NET Core web apps call:
- The HTTPS Redirection Middleware (UseHttpsRedirection) to redirect all HTTP requests to HTTPS.
- UseHsts, HTTP Strict Transport Security Protocol (HSTS).
ASP.NET Core Enforce HTTPS
The .UseHttpsRedirection() will issue HTTP response codes redirecting from http to https. The .UseHsts() will add the HSTS response header which the client is supposed to obey.
Notable comments
Nate (2 upvotes): They do not block incoming requests. UseHttpsRedirection should issue a redirect from http to https. UseHsts is a header to remind the browser that when they come back to this site, skip the initial http request, and go directly to https. If you want to block http completely, you’d need to do that on your host (iis, apache, etc).
Originally posted on Stack Overflow — 49 upvotes (accepted answer). Licensed under CC BY-SA.
Wednesday, September 5, 2018 by Nate Bross
I switched jobs in March of this year, that is a story for a different time, but the important thing is that it reintegrated me with some technology I had not used in quite some time: FileMaker. FileMaker is a database system that provides UI and basic scripting capabilities all in a single package. Users use the FileMaker client to access data stored in FileMaker databases, utilizing layouts and scripts in the database. Its an all in one system.
As a web developer with a lot of C# code under my belt, I wanted to connect to data in FileMaker from the outside. Historically the way to do this was through their XML Publishing Engine, I picked up an existing open source project and modified it to suit my needs. Ten years ago, this was a great solution and it worked well. It still does, but as things change so must we. In FileMaker 17 the Data API was introduced. It uses JSON and is RESTful. A new package was needed. Enter my side project: FMData. I built this as a learning exercise, but quickly realized it could be useful to other developers.
The library provides coverage for the FileMaker 17 Data API, and I’ve just released v2.1, cleaning up a handful of bugs and improving coverage of the underlying FileMaker API. I still don’t have full coverage, but I’m inching towards it. I have tons of ideas for features and enhancements, so be sure to keep an eye on the project. If you find it useful let me know. If you find a bug, open an issue. If you have a feature idea, open an issue on github and consider making a pull request.
The package is available on Nuget, and getting some data is really this simple:
var client = new FileMakerRestClient("server", "fileName", "user", "pass"); // without .fmp12
var toFind = new Model { Name = "someName" };
var results = await client.FindAsync(toFind);
// results = IEnumerable<Model> matching with Name field matching "someName" as a FileMaker Findrequest.
That’s all there is to it, and there’s more examples on the project site: https://fmdata.io/