IdentityServer4 : Service to service call fails to find Audience - asp.net-core

I am trying to make use of IdentityServer4 for authenticating the user for a Micoservices architecture. When I am trying to make a call from my Web App controller to another Web API service, the call fails on the Web API service with the message on the console that looks like below:
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAudienceException:
IDX10214: Audience validation failed.
Audiences: 'null/resources'. Did not match: validationParameters.ValidAudience: 'hierarchy' or validationParameters.ValidAudiences: 'null'.
My chain of call is something like below:
[User] --> [ASP.Net core MVC: Web App ]--> [ASP.Net core MVC: Web Api ]
I have created an Identity server using the IdentityServer4 with the following configuration:
API resources
new ApiResource("hierarchy", "Hierarchy Configuration API"),
new ApiResource("deviceconfiguration", "Device Configuration API"),
Client
new Client
{
ClientId = "system.health.check",
ClientName = "System health check client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"healthcheck"
},
RedirectUris = { "http://localhost:5100/signin-oidc", "http://localhost:5103/signin-oidc",},
PostLogoutRedirectUris = { "http://localhost:5100/signout-callback-oidc" },
}
Service - Startup
services.AddIdentityServer(x => x.IssuerUri = "null")
.AddSigningCredential(Certificate.Certificate.Get())
.AddTemporarySigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetUsers());
[ASP.Net core MVC: Web App ]
I have setup my opthion as below:
"OpenIdConnectOptions": {
"AuthenticationScheme": "oidc",
"SignInScheme": "Cookies",
"Authority": "http://localhost:5000",
"RequireHttpsMetadata": false,
"ClientId": "system.health.check",
"ClientSecret": "secret",
"ResponseType": "code id_token",
"GetClaimsFromUserInfoEndpoint": true,
"SaveTokens": true
}
the configure code on the startup looks like this:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies"
});
var option1 = new OpenIdConnectOptions();
Configuration.GetSection("Security:OpenIdConnectOptions").Bind(option1);
app.UseOpenIdConnectAuthentication(option1);
This works well so far and I can see that when the user is not Authenticated, he is directed to the Identity server and the token is issues.
Now the issue is that I want call form this service to be routed to another Web API service that is also protected via scope of say hierarchy as shown below:
Service to service call
var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");
var requestMessage = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5103/api/Hierarchy/22");
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var _client = new HttpClient();
var response = await _client.SendAsync(requestMessage);
return await response.Content.ReadAsStringAsync();
[ASP.Net core MVC: Web Api ]
Startup is setup same as my other service but as this is a resource service my configuration is different:
"IdentityServerAuthenticationOptions": {
"Authority": "http://localhost:5000",
"RequireHttpsMetadata": false,
"ApiName": "hierarchy",
"AllowedScopes": [
"openid",
"hierarchy"
]
the configure code on the startup looks like this:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies"
});
var option2 = new IdentityServerAuthenticationOptions();
Configuration.GetSection("Security:IdentityServerAuthenticationOptions").Bind(option2);
app.UseIdentityServerAuthentication(option2);
Now the issue is that the call does not return anything and on the console of this service I see a log that says:
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAudienceException: IDX10214: Audience validation failed. Audiences: 'null/resources'. Did not match: validationParameters.ValidAudience: 'hierarchy' or validationParameters.ValidAudiences: 'null'.
at Microsoft.IdentityModel.Tokens.Validators.ValidateAudience(IEnumerable`1 audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateTokenPayload(JwtSecurityToken jwt, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.<HandleAuthenticateAsync>d__1.MoveNext()
when I decode the access token on my [Web App] this is what I see
{
"nbf": 1501764330,
"exp": 1501767930,
"iss": "null",
"aud": "null/resources",
"client_id": "system.health.check",
"sub": "2",
"auth_time": 1501764327,
"idp": "local",
"scope": [
"openid",
"profile"
],
"amr": [
"pwd"
]
}
so its clearly missing the Audience as well as scope healthcheck
any ideas as to why its missing the Audience or what I am missing here?

assuming your api name is "hierarchy", try passing this in Allowed Scopes in the client configuration. Here:
new Client
{
ClientId = "system.health.check",
ClientName = "System health check client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"healthcheck" <------
},
RedirectUris = { "http://localhost:5100/signin-oidc", "http://localhost:5103/signin-oidc",},
PostLogoutRedirectUris = { "http://localhost:5100/signout-callback-oidc" },
}
You can add it after healthcheck

Related

Asp.Net Core 3.1 role based authorization for Web Api does not work

I use the IdentityServer4 that comes with Asp.Net Core 3.1 to implement role based authorization.
On debugging, I see that claim http://schemas.microsoft.com/ws/2008/06/identity/claims/role is correctly set to "Administrator" on entering the server Web Api call. But,
[Authorize (Roles = "Administrator")] for the Web Api always fails returning 403 error. Note that the simple [Authorize] works fine.
I went through debugging steps described in https://github.com/dotnet/AspNetCore.Docs/issues/14944 without success. Appreciate if you can help to make role based authorization work.
Code snippet:
AddOpenIdConnect(IdentityServerConstants.ProtocolTypes.OpenIdConnect, opt =>
{
opt.Authority = "http://localhost:44369";
opt.RequireHttpsMetadata = false;
opt.ClientId = "mvc";
opt.ClientSecret = "secret";
opt.ResponseType = "code";
opt.SaveTokens = true;
opt.GetClaimsFromUserInfoEndpoint = true;
opt.Scope.Add("roles");
opt.ClaimActions.MapUniqueJsonKey("roles", "role");
opt.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
};
}).
Decoded JWT below:
{
"nbf": 1606797785,
"exp": 1606801385,
"iss": "https://localhost:44369",
"aud": "BaselineAPI",
"client_id": "Baseline",
"sub": "38ba2f2e-100d-eb11-ae75-00f48da696da",
"auth_time": 1606752334,
"idp": "local",
"role": "Administrator",
"scope": [
"openid",
"profile",
"BaselineAPI"
],
"amr": [
"pwd"
]
}
UPDATE:
Replacing the Role with Policy based authorization works.
Add this to Startup.cs:
services.AddAuthorization(options => {
options.AddPolicy("IsAdmin", policy => { policy.RequireClaim(ClaimTypes.Role, "Administrator"); });
});
Add this to your Api method:
[Authorize(Policy = "IsAdmin")]
The name of the Role claim name in IdentityServer is not the same as the name of the Role claim in ASP.NET Core. To map this you need to add this code:
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
};
If you also want to add additional claims, you need to explicitly map them into the local user ClaimsPrincipal using:
options.ClaimActions.MapUniqueJsonKey("claimname", "claimname");
By default these claims are not included in the user object.

ID Token in Resource Owner Password Grant in IdentityServer4

I have implemented IdentityServer 4 in a .Net Core API project.
I created a Resource Owner Credentials Grant client and allowed openID scopes.
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.Ids)
.AddInMemoryApiResources(Config.Apis)
.AddInMemoryClients(Config.Clients).
AddResourceOwnerValidator<CustomResourceOwnerPasswordValidator>();
Here's my client code:
new Client
{
ClientId = "resourceownerclient",
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
AccessTokenType = AccessTokenType.Jwt,
AlwaysSendClientClaims = true,
Enabled = true,
ClientSecrets= new List<Secret> { new Secret("Secret".Sha256()) },
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.OfflineAccess,
"api1"
}
}
I have also added all the scopes in AddInMemoryIdentityResources().
But the response does not contain ID token.
{
"access_token": "eyJhbGciOiJSUzI1NiIsImt",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "api1 openid"
}
Looked at the other question on the SO on whether the Open ID Connect supports the Resource Owner Credentials Grant.
How can I get ID token in the response?
The refresh token call returns ID token but it's a bug.

Identity server issues AuthenticationScheme: Bearer was challenged for a token obtained by client credentials. How to find out the underlying error?

I have trouble authenticating some integration tests requests when working with Identity Server 4 (ASP.NET Core 3.1).
My setup is as follows:
Identity server configuration
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Email(),
new IdentityResources.Profile(),
};
}
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("resourceapi", "Resource API")
{
Scopes = {new Scope("api.read")}
}
};
}
public static IEnumerable<Client> GetClients()
{
return new[]
{
new Client
{
RequireConsent = false,
ClientId = "MY_CLIENT_ID",
ClientName = "My Client Name",
// code is required for SPA, client credentials for test runner
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
AllowedScopes = {"openid", "profile", "email", "api.read"},
RedirectUris = {"http://localhost:4201/auth-callback"},
PostLogoutRedirectUris = {"http://localhost:4201/"},
AllowedCorsOrigins = {"http://localhost:4201"},
AllowAccessTokensViaBrowser = true,
AccessTokenLifetime = 3600,
RequireClientSecret = false
}
};
}
services.AddIdentity<AppUser, IdentityRole>()
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
services.AddIdentityServer()
.AddDeveloperSigningCredential()
// this adds the operational data from DB (codes, tokens, consents)
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder => builder.UseSqlServer(Configuration.GetConnectionString("Default"));
// this enables automatic token cleanup. this is optional.
options.EnableTokenCleanup = true;
options.TokenCleanupInterval = 30; // interval in seconds
})
//.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<AppUser>();
ASP.NET Core client
// this is called from Startup.ConfigureServices
public static void ConfigureSecurity(this IServiceCollection services, IConfiguration configuration)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.Authority = configuration.GetSection("Idam").GetValue<string>("BaseUrl"); // http://localhost:54916
o.Audience = configuration.GetSection("Idam").GetValue<string>("Audience"); // "resourceapi"
o.RequireHttpsMetadata = false;
});
services.AddAuthorization();
}
Integration tests code
var client = new HttpClient();
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = $"{IdentityServerUrl}/connect/token",
ClientId = "MY_CLIENT_ID",
ClientSecret = IdentityServerPass,
Scope = "api.read"
}).ConfigureAwait(false);
tokenResponse.HttpResponse.EnsureSuccessStatusCode();
Here I receive a bearer token, but does not seem to be accepted (Identity Server issues the error below). It looks like the following:
{
"nbf": 1587392198,
"exp": 1587395798,
"iss": "http://localhost:54916",
"aud": "resourceapi",
"client_id": "STACKOVERFLOW_METRO_MIRROR",
"scope": [
"api.read"
]
}
> IdentityServer4.Hosting.IdentityServerMiddleware: Information:
> Invoking IdentityServer endpoint:
> IdentityServer4.Endpoints.TokenEndpoint for /connect/token
> IdentityServer4.Validation.TokenRequestValidator: Information: Token
> request validation success, { "ClientId":
> "STACKOVERFLOW_METRO_MIRROR", "ClientName": "My Client Name",
> "GrantType": "client_credentials", "Scopes": "api.read", "Raw": {
> "grant_type": "client_credentials",
> "scope": "api.read",
> "client_id": "MY_CLIENT_ID",
> "client_secret": "***REDACTED***" } } Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request
> starting HTTP/1.1 GET
> http://localhost:44324/api/GeneralData/GetAllTags
> Microsoft.AspNetCore.ResponseCaching.ResponseCachingMiddleware:
> Information: No cached response available for this request.
> Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:
> Information: Authorization failed.
> Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:
> Information: AuthenticationScheme: Bearer was challenged.
Since my setup also includes a SPA that is able to successfully authenticate (login via Identity Server login form -> get token -> API uses token with success), I decrypted such a token to see if I see any relevant difference that might reveal what I am missing for the testing authentication flow:
{
"nbf": 1587393059,
"exp": 1587396659,
"iss": "http://localhost:54916",
"aud": "resourceapi",
"client_id": "MY_CLIENT_ID",
"sub": "fd351b3b-dfb2-4f2f-8987-af9d23c9dc6e",
"auth_time": 1587393055,
"idp": "local",
"given_name": "test",
"email": "test#example.com",
"scope": [
"openid",
"email",
"profile",
"api.read"
],
"amr": [
"pwd"
]
}
ASP.NET Core Web API Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureCustomServices();
services.ConfigureSettings(Configuration);
services.ConfigureSecurity(Configuration);
services.ConfigureMvc();
services.BindLogging();
services.ConfigureRedisCache(Configuration);
services.ConfigureApiExplorer();
services.AddHttpContextAccessor();
services.AddDbContext(Configuration);
ConfigureAuditNet();
services.AddCorsAndPolicy();
services.ConfigureHangfire(Configuration);
services.AddSignalR();
services.AddAutoMapper(typeof(QuestionProfile).Assembly);
services.AddHealthChecks();
services
.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
ILoggingService logger, IHostApplicationLifetime lifetime, IServiceProvider serviceProvider,
ISoApiDailyRequestInfoService soApiDailyRequestInfoService)
{
app.UseResponseCaching();
app.UseMiddleware<ResponseTimeMiddleware>();
app.ProtectHangfireDashboard();
app.ConfigureExceptionPage(env);
app.StartHangFireJobs(serviceProvider, Configuration);
ConfigureApplicationLifetime(logger, lifetime, soApiDailyRequestInfoService);
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
app.EnsureAppUserMiddleware();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<PostHub>("/post");
endpoints.MapHealthChecks("/health");
});
app.ConfigureAuditMiddleware();
app.UseSwagger();
}
Unfortunately, the Identity server provides a very generic error and I do see what I missing here.
Question: Identity server issues AuthenticationScheme: Bearer was challenged for a token obtained by client credentials. How to find out the underlying error?
In stratup.cs Configure method , Please make sure that sequence of app.Use... are correct.
E.g. app.UseAuthentication(); before app.UseMvc()

asp.net mvc login from identityserver4 with a invalid_request error

today i use the demo of identityserver4 Build a validation server, and i can use the asp.net core client with openid login the client.
but i could not login my asp.net mvc5 client with openid, The error of the prompt is : invalid_request,
here is my identityserver4 config code with getclient()
// clients want to access resources (aka scopes)
public static IEnumerable<Client> GetClients()
{
// client credentials client
return new List<Client>
{
// OpenID Connect hybrid flow and client credentials client (MVC)
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
RequireConsent = true,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:5002/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
},
AllowOfflineAccess = true
}
};
}
}
and the follow code is my asp.net mvc5 clent ConfigureAuth(),because the idenetiyServer4 define the ClientSecrets is "secret".Sha256(),so in this mvc client , i set the ClientSecret = GetSHA256HashFromString("secret"),i create prvate the method GetSHA256HashFromString() to convert the string to sha256.
here is my code:
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "http://localhost:5000", //ID Server SSO Server
ClientId = "mvc",
ClientSecret = GetSHA256HashFromString("secret"),
ResponseType = "code id_token",
RedirectUri = "http://localhost:5002/signin-oidc", //URL of Client website
PostLogoutRedirectUri = "http://localhost:5002/signout-callback-oidc", //URL of Client website
Scope = "api1",
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
RequireHttpsMetadata = false,
});
and i press f5 to run the mvc client ,and press the button of login,the brower can jump to the localhost:5000,but it is give me a error:
Sorry, there was an error : invalid_request and the other error info are :
Request Id: 0HL9RHBTJIT3T:00000003**
thanks a lot.
The value of ClientSecret should be the actual secret value not the hashed one.
The secret is stored as hash when you use a persisted data storage to prevent an attacker to obtain your client's secrets in case if your storage is compromised.
In your case, The secret value is "secret". So the code will be
ClientSecret = "secret"

Set AccessToken Validation on a .net wepapi 2 (not core) with Identity server 4

I know how to setup IdentityServer 4 Authentication in .Net core. That is: using the extensions defined in IdentityServer4.AccessTokenValidation. And I would set it up in my startup class like so:
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
ApiName = "webapi"
});
The problem is that now I need to make authenticated requests to a .net 4.6 web api2 (not core). And the same package doesn't work for that.
According to this question all I have to do is to use the same package that was used for Identity server 3:IdentityServer3.AccessTokenValidation.
But After trying it out all I get is 401 when making requests to the web api. And I don't know how to wire authentication events to understand the reason behind it. Here is my configuration:
Api Startup.cs:
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://localhost:5000",
RequiredScopes = new[] { "webapi" },
});
Client Startup.cs:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "http://localhost:5000",
RedirectUri = "http://localhost:3954/signin-oidc",
ClientId = "MvcClient",
Scope = "openid profile webapi offline_access",
ResponseType = "code id_token",
ClientSecret = "secret",
UseTokenLifetime = false,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
},
});
TestController in the Client project:
var tokenClient = new TokenClient("http://localhost:5000/connect/token", "MvcClient", "secret");
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("webapi");
var client = new HttpClient();
client.SetBearerToken(tokenResponse.AccessToken);
var content = await client.GetStringAsync("http://localhost:5004/api/identity");
I successfully get an access token here. But get a 401 when making the request to api/identity.
Here is the Config in the IDP:
new ApiResource("webapi", "My API")
[...]
new Client
{
ClientId = "MvcClient",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
RequireConsent = true,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:3954/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:3954/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"netcoremvcapi",
"webapi"
},
AllowOfflineAccess = true,
}
Any idea why this might be failing? Am I making the wrong assumption that I can use IdentityServer3.AccessTokenValidation to validate the token?
The first issue was that my startup class wasn't being called. That was solved by adding the Microsoft.Owin.Host.SystemWeb package.
Then it got interesting. I started getting an exception while registering OpenIdConnectAuthenticationMiddleware into the OWIN runtime:
Could not load type 'System.IdentityModel.Tokens.TokenValidationParameters' from assembly
'System.IdentityModel.Tokens.Jwt, Version=5.0.0.127
And the reason is because my web api 2 project is using the Owin implementation Katana. And apparently Katana does not support v5.0 of that package. As described here and here.
So for that I had to remove that package first (downgrade didn't work due to dependency). That also failed because of the dependency on my version of the package Microsoft.Owin.Security.Jwt. And that, of course, led to another issue with a dependency of another package....after removing all the dependant packages and reinstalling System.IdentityModel.Tokens.Jwt v4.0.2 and then IdentityServer3.AccessTokenValidation. It all worked with the same setup described above...