ID Token in Resource Owner Password Grant in IdentityServer4 - asp.net-core

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.

Related

IdentityServer4 ADFS External Not Returning Roles

I have created an IdentityServer4 IDP using the standard template for Core Identity. I am looking to have an External provider being out ADFS 2016 Server. I have added this to the AddAuthentication() in Startup.cs
services.AddAuthentication()
.AddOpenIdConnect("adfs", "ADFS", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.Authority = "[AuthURL]";
options.ClientId = "[ClientId]";
options.ResponseType = "id_token token code";
options.Scope.Add("profile");
options.Scope.Add("email");
options.CallbackPath = "/signin-adfs";
options.SignedOutCallbackPath = "/signout-callback-adfs";
options.RemoteSignOutPath = "/signout-adfs";
options.ClaimActions.Add(new JsonKeyClaimAction("role", null, "role"));
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
It successfully redirects to the ADFS login window. Once signed in it calls the ExternalController.cs CallBack() correctly and I do have a successful authentication.
public async Task<IActionResult> Callback()
{
// read external identity from the temporary cookie
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
The issue I am having is that I am not getting back the full user. I see claims but I do not see roles. I am used to seeing a JWT token which includes a list of Roles, however, I do not see these roles within the Result from above.
How can I either get a JWT token back from the Authentication against ADFS or have the roles returned and be within the Result?
Inside OIDC add scope for roles:
options.Scope.Add("roles");
Inside config.cs in IDS4 make sure your client has the allowed scopes:
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.OfflineAccess,
"roles",
},
If it's an API, make sure config.cs has the correct userclaims:
new ApiResource()
{
Name = "API",
DisplayName = "display",
ApiSecrets =
{
new Secret("secret".Sha256()),
new Secret
{
Value = "4A04D56554F731CCD123BB574D6918C8C83BDF65",
Type = "X509Thumbprint",
Description = "Certificate"
}
},
Scopes = new List<string>(){"postman Web API"},
Enabled = true,
UserClaims =
{
JwtClaimTypes.Name,
JwtClaimTypes.Email,
JwtClaimTypes.Subject,
JwtClaimTypes.Role,
JwtClaimTypes.Address,
JwtClaimTypes.Confirmation,
JwtClaimTypes.EmailVerified,
JwtClaimTypes.Id,
JwtClaimTypes.Profile
}
},

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.

Keycloak backchannel login with .NET core api?

According to this OpenID spec https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html
OpenID Connect Client Initiated Backchannel Authentication Flow is an authentication flow like OpenID Connect. However, unlike OpenID Connect, there is direct Relying Party to OpenID Provider communication without redirects through the user's browser. This specification has the concept of a Consumption Device (on which the user interacts with the Relying Party) and an Authentication Device (on which the user authenticates with the OpenID Provider and grants consent). This specification allows a Relying Party that has an identifier for a user to obtain tokens from the OpenID Provider. The user starts the flow with the Relying Party at the Consumption Device, but authenticates and grants consent on the Authentication Device.
I'm trying to do the same with my .NET core api using Keycloak as the IdentityProvider. This is current working setup with Authorization Code Flow
var oidcOptions = new OpenIdConnectAuthenticationOptions()
{
AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType,
ClientId = Application.Config.KeycloakConfig.Default.ClientId,
Authority = Application.Config.KeycloakConfig.Default.Authority,
RedirectUri = Application.Config.KeycloakConfig.Default.SiteManagementAuthRedirectUri,
ClientSecret = Application.Config.KeycloakConfig.Default.ClientSecret,
RequireHttpsMetadata = false,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
Scope = "openid",
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
RequireExpirationTime = true,
IssuerValidator = (issuer, securityToken, validationParameters) =>
{
if (issuer != Application.Config.KeycloakConfig.Default.Authority)
throw new SecurityTokenInvalidIssuerException("Invalid issuer");
return issuer;
},
ValidAudience = Application.Config.KeycloakConfig.Default.ClientId
},
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
RedirectToIdentityProvider = OnRedirectToIdentityProvider
}
};
appBuilder.UseOpenIdConnectAuthentication(oidcOptions);
I managed to get the tokens using password grant as below
HTTP POST {{KeyCloakUrl}}/realms/My-Realms/protocol/openid-connect/token
{
grant_type: "password",
client_id: "Keycloak_client_id",
client_secret: "***",
username: "username i created in Keycloak",
password: "***",
response_type : "code id_token",
scope: "openid"
}
Reponse:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI***********",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJ********",
"token_type": "bearer",
"id_token": "eyJhbG**********",
"not-before-policy": 0,
"session_state": "c2803e20-bc04-4a9c-9b1a-d1fd3dfd1f22",
"scope": "openid email profile"
}
Question: Is it possible to manual make a POST request with id_token to the .NET core api to authenticate the requests and how to do it?

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"

IdentityServer4 : Service to service call fails to find Audience

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