1
votes

I've followed the Identity Server 4 docs and created a working test instance locally. I've then added an example Javascript client following this tutorial. This all works fine whilst still using the test users and clients.

I now want to start breaking out the test data into production-ready services. I thought I'd start with my clients. Here is the Identity Server setup code from the ConfigureServices method in my Startup:

var builder = services.AddIdentityServer()
    .AddDeveloperSigningCredential()
    .AddInMemoryIdentityResources(Config.GetIdentityResources())
    .AddInMemoryApiResources(Config.GetApiResources())
    //.AddInMemoryClients(Config.GetClients())
    .AddTestUsers(Config.GetUsers());

builder.Services.AddTransient<IdentityServer4.Stores.IClientStore, CustomClientStore>();

Literally the only thing I've changed here is commenting out the AddInMemoryClients line and adding the last line which registers a new client store. Right now all this CustomClientStore class does is replicate the Config.GetClients() test data.

However, when I now run my demo Javascript app and click my Login button, I now get the following Javascript error:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:54458/.well-known/openid-configuration. (Reason: CORS header 'Access-Control-Allow-Origin' missing).

http://localhost:54458 is my IS4 instance obviously. I haven't touched my CORS configuration, and if I comment out the AddTransient line above and restore the AddInMemoryClients line everything works again.

Can anyone help me understand this?

2
Do you get any exceptions? The exception middleware clears/removes (when an exception is thrown) any CORS header set by the middleware before - Tseng
No exceptions, no. But I've now answered my own question. See below. - Tom Troughton

2 Answers

3
votes

I found my answer by poking inside Identity Server's own code. It turns out that when registering the test client store using the AddInMemoryClients extension, Identity Server is also implementing a CORS policy at the same time. You can see this code here.

This CORS policy uses another in-memory implementation, this time of ICorsPolicyService. This implementation takes the AllowedCorsOrigins properties of the configured clients to determine if a requesting origin is allowed.

So my solution was to create this extension method...

public static IIdentityServerBuilder AddCustomCorsPolicy(this IIdentityServerBuilder builder)
{
    var existingCors = builder.Services.Where(x => x.ServiceType == typeof(ICorsPolicyService)).LastOrDefault();
    if (existingCors != null &&
        existingCors.ImplementationType == typeof(DefaultCorsPolicyService) &&
        existingCors.Lifetime == ServiceLifetime.Transient)
    {
        builder.Services.AddTransient<ICorsPolicyService, CustomCorsPolicyService>();
    }

    return builder;
}

...then implement CustomCorsPolicyService to check the requesting origin against those setup in my client base, then add AddCustomCorsPolicy to my Identity Server setup.

1
votes

In your Javascript request you need to set the following field:

request.credentials

See here for the docs, you probably want same-origin, but if it is going to be a fully internal infrastructure then it ok to use `include'. An example:

import fetch from 'isomorphic-fetch'

return fetch(`${API_PATH}${SIGN_IN_PATH}`, {
  method: 'POST',
  credentials: 'include',
  headers: ...,
  body: ...
});

In your Configure method of Startup.cs you will also need to enable CORS using:

app.UseCors(CorsOptions.AllowAll);

You can build your own CorsOptions instance and pass this in if AllowAll is not suitable. More reference docs here and here.