So, I'm building a new security token service that is replacing an old solution based on IdentityServer3. I wish to authenticate with PKCE flow, and so far I've configured the new service with a certificate, identity resources, a test user, an API resource, and a client.
Identity resources
IEnumerable<IdentityResource> IdentityResources => new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource
{
Name = JwtClaimTypes.Role,
UserClaims = new List<string> { JwtClaimTypes.Role }
}
};
User
List<TestUser> Users => new List<TestUser>
{
new TestUser
{
SubjectId = "1234",
Username = "asd",
Password = "asd",
Claims = new [] { new Claim(JwtClaimTypes.Role, "super") }
}
};
API resource
IEnumerable<ApiResource> Apis => new List<ApiResource>
{
new ApiResource
{
Name = "api",
UserClaims = new List<string> { JwtClaimTypes.Role },
Scopes = new List<Scope> { new Scope("api") }
}
};
Client
IEnumerable<Client> Clients => new List<Client>
{
new Client
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.Code,
AllowAccessTokensViaBrowser = true,
RequirePkce = true,
RequireClientSecret = false,
RequireConsent = false,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
JwtClaimTypes.Role,
"api"
},
AllowedCorsOrigins = { "http://localhost:3005/" },
RedirectUris = { "http://localhost:3005/callback.html" },
PostLogoutRedirectUris = { "http://localhost:3005/index.html" }
}
};
The AngularJS client is using oidc-client.js. The communication between this and my token service seems to be working fine. I get valid JWT tokens returned from IdentityServer4, containing the data I expect. My goal is to pass the user's ID contained in an access token to the API, so it can make sure the user is authenticated through an external library built specifically for checking if the user has "super" role, and make calls to the DB with the user ID.
The client configures an OIDC UserManager like this:
return new Oidc.UserManager({
client_id: 'client',
response_type: 'code',
filterProtocolClaims: true,
loadUserInfo: true,
revokeAccessTokenOnSignout: true,
scope: 'openid profile role api',
authority: 'https://localhost:44300/',
redirect_uri: 'http://localhost:3005/callback.html',
post_logout_redirect_uri: 'http://localhost:3005/index.html'
});
Now, this is an old codebase - but it seems to me that when calls are made from the client to the API - access tokens are put in an Authorization header in a push() call using $httpProvider.interceptors, like this..
$httpProvider.interceptors.push(function (apiUrl, oidcManager, currentLanguage) {
return {
'request': function (config) {
if (isApiCall(config, apiUrl, oidcManager)) {
var userManager = oidcManager.getUserManager();
userManager.getUser().then(function (user) {
if (!user) {
userManager.signinRedirect();
}
config.headers.Authorization = 'Bearer ' + user.access_token;
});
}
return config;
}
};
});
When logging the config object, I can verify that the Authorization header indeed contains the access token that I need. However, when calling the API with a simple GET request with no required input parameters - protected by an [Authorize] attribute on the action method, I get a 401 Not Authorized response. The API is configured like this, in the Startup class..
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseIdentityServerBearerTokenAuthentication(
new IdentityServerBearerTokenAuthenticationOptions {
Authority = "https://localhost:44300/",
RequiredScopes = new[] { "api" }
}
);
Now, setting a breakpoint in the controller constructor for the endpoint I'm trying to hit, I can see that no user data, claims, etc. are being picked up. I'm guessing this has to do with how the old AngularJS client is constructing and passing these requests, but so far I'm stuck!
Any insight and guidance on this problem would be much and greatly appreciated - and thanks for reading my long mess!