I've 3 projects in my solution one is Idp using IdentityServer4 with the following configuration. I'm using Implicit Flow
public void ConfigureServices(IServiceCollection services)
{
var cert = new X509Certificate2(Path.Combine(_environment.ContentRootPath, "damienbodserver.pfx"), "");
// Add framework services.
var connectionString = Configuration.GetConnectionString("PostgreSQLConnectionString");
Console.WriteLine(connectionString);
services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(connectionString));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//NB added for implicit flow
services.AddCors();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, AppClaimsPrincipalFactory>();
services.AddIdentityServer()
//.AddTemporarySigningCredential()
.AddSigningCredential(cert)
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(IdentityServerStatics.GetIdentityResources())
.AddInMemoryApiResources(IdentityServerStatics.GetApiResources())
.AddClientStore<CustomClientStore>() // Add the custom client store
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<CustomProfileService>(); // use custom profile service to pull in claims
services.AddAuthorization(options =>
{
options.AddPolicy("MyUMD_User", policy => policy.RequireClaim("MyUMD:AccessLevel", "User", "Manage", "Support", "Admin"));
});
services.AddAuthorization(options =>
{
options.AddPolicy("MyUMD_Manage", policy => policy.RequireClaim("MyUMD:AccessLevel", "Manage", "Support", "Admin"));
});
services.AddAuthorization(options =>
{
options.AddPolicy("MyUMD_Support", policy => policy.RequireClaim("MyUMD:AccessLevel", "Support", "Admin"));
});
services.AddAuthorization(options =>
{
options.AddPolicy("MyUMD_Admin", policy => policy.RequireClaim("MyUMD:AccessLevel", "Admin"));
});
}
and the resource server is configured as below.
NOTE this resource server is already working with custom JWT token generation and validation. Now I want to add additional capability to read the claims from the token generated by IdentityServer4
public void ConfigureJwtAuthService(IServiceCollection services)
{
var folderForKeyStore = Configuration["Production:KeyStoreFolderWhichIsBacked"];
var cert = new X509Certificate2(Path.Combine(_env.ContentRootPath, "damienbodserver.pfx"), "");
// Important The folderForKeyStore needs to be backed up.
services.AddDataProtection()
.SetApplicationName("vast_webapplication")
.PersistKeysToFileSystem(new DirectoryInfo(_env.ContentRootPath))
.ProtectKeysWithCertificate(cert);
var symmetricKeyAsBase64 = "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==";
var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
var signingKey = new SymmetricSecurityKey(keyByteArray);
// JWT Token signing settings
TokenAuthOptions tokenAuth = new TokenAuthOptions()
{
Audience = "vast-audience",
Issuer = "vast-issuer",
// this gets set later in Configure
SigningCredentials = null,
Key = signingKey
};
// add the auth options to the DI container
services.AddSingleton(tokenAuth);
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = "vast-issuer",
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = "vast-audience",
// Validate the token expiry
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(60)
};
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.TokenValidationParameters = tokenValidationParameters;
});
//.AddCookie(options=> options.Cookie.Domain="localhost:5000");
services.AddAuthorization(options => {
//options.DefaultPolicy= new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme).RequireAuthenticatedUser().Build();
options.AddPolicy(AuthorizationPolicies.TicketTypeRead, policy => {
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim(CustomClaims.TicketTypRead);
});
string[] roles = new string[] { "User", "Management" };
options.AddPolicy("Reporting_Managers", policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim("SkyBusReporting:Reporting", "Management");
});
});
}
Angular app is also a .net project and it successfully calls the api (resource server function). In response I received a 401 Unauthorized status and in the header I can see this
www-authenticate →Bearer error="invalid_token", error_description="The signature key was not found"
What I'm doing wrong here?
thanks to aaronR i figured out the issue in my code. Now I'm not sure if this is the best way to implement the implicit flow
I changed my code is resource api to use the public key from the certificate to read the token which is missing in the above code.
In the ConfigureJwtAuthService function above I changed the code to get a key from the certificate as below
var cert = new X509Certificate2(Path.Combine(_env.ContentRootPath, "damienbodserver.pfx"), "");
X509SecurityKey key = new X509SecurityKey(cert);
SigningCredentials credentials = new SigningCredentials(key, "RS256");
than I used this key in TokenValidationParameters object as below
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
//IssuerSigningKey = signingKey,
IssuerSigningKey = key,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = false,
ValidIssuer = "vast-issuer",
// Validate the JWT Audience (aud) claim
ValidateAudience = false,
ValidAudience = "vast-audience",
// Validate the token expiry
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(60)
};
I also changed the implementation of custom JWT Token generation to use the same key and credentials to generate the JWT Token.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With