I'm trying to setup a solution with the following:
- IdentityServer4 instance
- React / js client
- ASP.NET Core API (protected by IdentityServer)
Since I want to use roles and claims, I would like to use a reference token (id_token) and have the API verify the claims against the IdentityServer.
Configuration for the IdentityServer instance:
"IdentityServer": {
"IdentityResources": [
"openid",
"email",
"phone",
"profile"
],
"ApiResources": [
{
"Name": "b2a6f5a1-9317-4b2f-bb02-c2f7cd70ce9a",
"DisplayName": "My API",
"ApiSecrets": [ { "Value": "<BASE 64 ENCODED SHA256 HASH OF SECRET>" } ]
}
],
"Clients": [
{
"Enabled": true,
"ClientId": "976d5079-f190-41a2-a6f6-be92470bacc0",
"ClientName": "My JS client",
"ClientUri": "http://localhost:3000",
"LogoUri": "logo.png",
"RequireClientSecret": false,
"AllowAccessTokensViaBrowser": true,
"RequireConsent": false,
"ClientClaimsPrefix": null,
"AccessTokenType": "reference",
"AllowedGrantTypes": [ "implicit" ],
"RedirectUris": [ "http://localhost:3000/authentication/login-callback" ],
"PostLogoutRedirectUris": [ "http://localhost:3000/authentication/logout-callback" ],
"AllowedCorsOrigins": [ "http://localhost:3000" ],
"AllowedScopes": [ "openid", "email", "phone", "profile", "b2a6f5a1-9317-4b2f-bb02-c2f7cd70ce9a" ]
}
]
}
Configuration for the (protected) API:
"Identity": {
"Authority": "https://localhost:44311",
"ApiName": "b2a6f5a1-9317-4b2f-bb02-c2f7cd70ce9a",
"ApiSecret": "<UNHASHED SECRET>"
}
Startup.cs for the API:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
})
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration.GetValue<string>("Identity:Authority");
options.ApiName = Configuration.GetValue<string>("Identity:ApiName");
options.ApiSecret = Configuration.GetValue<string>("Identity:ApiSecret");
});
I query IdentityServer for an id_token with the following parameters:
export const Settings: any = {
authority: "https://localhost:44311",
post_logout_redirect_uri: "http://localhost:3000/authentication/logout-callback",
redirect_uri: "http://localhost:3000/authentication/login-callback",
response_type: "id_token",
scope: "openid email profile phone b2a6f5a1-9317-4b2f-bb02-c2f7cd70ce9a"
};
I get the following error: Requests for id_token response type only must not include resource scopes.
If I change the scope to:
export const Settings: any = {
// ...
scope: "openid email profile phone" // removed (protected) api resource
};
it works and I get an id_token like this:
{
"nbf": 1573798909,
"exp": 1573799209,
"iss": "https://localhost:44311",
"aud": "976d5079-f190-41a2-a6f6-be92470bacc0",
"nonce": "d768a177af684324b30ba73116a0ae79",
"iat": 1573798909,
"s_hash": "HbWErYNKpgsiOIO82IiReA",
"sid": "vVWVhLnVLiCMdLSBWnVUQA",
"sub": "90f84a26-f756-4923-9d26-6104eef031ac",
"auth_time": 1573798909,
"idp": "local",
"preferred_username": "noreply",
"name": "noreply",
"email": "noreply#example.com",
"email_verified": false,
"amr": [
"pwd"
]
}
Note that the audience is 976d5079-f190-41a2-a6f6-be92470bacc0, which is the js client.
When I use this token on the protected API, it says:
Bearer error="invalid_token", error_description="The audience '976d5079-f190-41a2-a6f6-be92470bacc0' is invalid"
which is not that strange since the API has the id b2a6f5a1-9317-4b2f-bb02-c2f7cd70ce9a.
So my question is: Where am I wrong? How do I get the token for the correct audience?
The ID token will be validated by your client app (React/js) app to get user claims , so the audience is your client app's client ID . A token passe to your web api should be validated by web api , so the audience is web api's name .
The ID token contains information about an End-User which is not used to access protected resource , while Access token allows access to certain defined server resources .You can set response_type to id_token token , and add api name/scope to scope configuration . With implicit flow , after authentication , client will get one ID token and one Access token , you can now use access token to access the protected web api .
Related
Before getting into the issue, let me tell you what I am trying to achieve.
I need to implement sort of SSO in all of my applications. For which I want to use ASP.NET Zero solutions as
SSO Provider as well as Clients.
Is it possible or am I overthinking?
I am using ASP.NET Zero template: ASP.NET Core - MVC & jQuery
I am very new to IdentityServer and OpenId so please excuse for my silly mistakes if I have made.
In one of ABP project, I have added a static client to IdentityServer AppSettings like below.
First project's AppSettings - Hosted application
{
"ClientId": "localhost",
"ClientName": "MVC Client Demo",
"AllowedGrantTypes": [
"implicit"
],
"RequireConsent": "true",
"ClientSecrets": [
{
"Value": "test"
}
],
"RedirectUris": [
"https://localhost:44302/signin-oidc"
],
"PostLogoutRedirectUris": [
"https://localhost:44302/Account/Login"
],
"AllowedScopes": [
"openid",
"profile",
"email",
"phone",
"default-api"
],
"AllowOfflineAccess": "true"
}
Now from my second ABP project (localhost), I am trying to enable OpenId to authenticated through above server.
Second project's AppSettings - Running on localhost
"OpenId": {
"IsEnabled": "true",
"Authority": "https://[applicationname].azurewebsites.net/",
"ClientId": "localhost",
"ClientSecret": "test",
"ValidateIssuer": "true",
"ClaimsMapping": [
{
"claim": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
"key": "http://schemas.microsoft.com/identity/claims/objectidentifier"
}
]
}
However I am not getting any error, in logs I can see there is a message that says:
AuthenticationScheme: Identity.External signed in.
And a cookie is being created with key "Identity.External" but login-is not happening successfully.
Inside AccountController below line returns null and that resulting into unsuccessful login.
**var externalLoginInfo = await _signInManager.GetExternalLoginInfoAsync();**
if (externalLoginInfo == null)
{
Logger.Warn("Could not get information from external login.");
return RedirectToAction(nameof(Login));
}
Try adding
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
before services.AddAuthentication()
This will map sub claim to NameIdentifier claim so GetExternalLoginInfoAsync will not return null.
That's my scenario.
Identity: https://mydomain.subdomain.com.br/homol/identity
When access the endpoint https://mydomain.subdomain.com.br/homol/identity/.well-known/openid-configuration this is my response
{
"issuer":"http://mydomain.subdomain.com.br/",
"jwks_uri":"http://mydomain.subdomain.com.br/.well-known/openid-configuration/jwks",
"authorization_endpoint":"http://mydomain.subdomain.com.br/connect/authorize",
"token_endpoint":"http://mydomain.subdomain.com.br/connect/token",
"userinfo_endpoint":"http://mydomain.subdomain.com.br/connect/userinfo",
"end_session_endpoint":"http://mydomain.subdomain.com.br/connect/endsession",
"check_session_iframe":"http://mydomain.subdomain.com.br/connect/checksession",
"revocation_endpoint":"http://mydomain.subdomain.com.br/connect/revocation",
"introspection_endpoint":"http://mydomain.subdomain.com.br/connect/introspect",
"device_authorization_endpoint":"http://mydomain.subdomain.com.br/connect/deviceauthorization",
"frontchannel_logout_supported":true,
"frontchannel_logout_session_supported":true,
"backchannel_logout_supported":true,
"backchannel_logout_session_supported":true,
"scopes_supported":[
"openid",
"email",
"profile"
],
"claims_supported":[
"sub",
"email_verified",
"email",
"updated_at",
"locale",
"zoneinfo",
"birthdate",
"website",
"picture",
"profile",
"preferred_username",
"nickname",
"middle_name",
"given_name",
"family_name",
"name",
"gender"
],
"grant_types_supported":[
"authorization_code",
"client_credentials",
"refresh_token",
"implicit",
"password",
"urn:ietf:params:oauth:grant-type:device_code"
],
"response_types_supported":[
"code",
"token",
"id_token",
"id_token token",
"code id_token",
"code token",
"code id_token token"
],
"response_modes_supported":[
"form_post",
"query",
"fragment"
],
"token_endpoint_auth_methods_supported":[
"client_secret_basic",
"client_secret_post"
],
"subject_types_supported":[
"public"
],
"id_token_signing_alg_values_supported":[
"RS256"
],
"code_challenge_methods_supported":[
"plain",
"S256"
],
"request_parameter_supported":true
}
Two questions:
1 - Why sub domain was removed?
2 - When the user access SPA application and then the oidc client redirect him (using siginRedirect method) to Login, the endpoint it was not found, because sub domain was removed again.
Thanks.
By default, Identityserver, the origin name is inferred from the request
you can read more Identityserver options here - http://docs.identityserver.io/en/3.1.0/reference/options.html
you can configure Identityserver to use your custom origin
services.AddIdentityServer(options =>
{
options.PublicOrigin ="https://mydomain.subdomain.com.br/homol/identity";
})
I've successfully setup a Blazor application to authenticate with the Azure tenant where I work. The authentication works beautifully. I have the App Registration setup in Azure with appRoles defined in the manifest. I've add a few users to the application with those roles assigned however I'm not getting any Role claims back on the user context after it authenticates.
Startup.cs
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
Manifest:
"appRoles": [
{
"allowedMemberTypes": [
"User"
],
"description": "Coming soon.",
"displayName": "Viewer",
"id": "{guid goes here}",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "Viewer"
},
{
"allowedMemberTypes": [
"User"
],
"description": "Coming soon.",
"displayName": "Manager",
"id": "{guid goes here}",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "Manager"
}
],
I'm trying to retrieve those roles with the claims after authentication, but no roles are coming through. This is obviously making IsInRole not work and for the life of me I can't find any samples of code to achieve this.
I would greatly appreciate it if someone could point me in the right direction!
If the user has been assigned application roles, it should be returned in the id token. You can decode the id token by using https://jwt.io/.
I didn't find a blazor sample, but you can refer to this aspnetcore sample.
Right now I can log out of Identity Server. But when logging back in, I can just select my email address - without having to reenter my password - to log in though Google to access my app.
I want to have to reenter my password (because the device is shared between multiple users). I followed the documentation, but I must be missing something.
(I am using a MVC client to test things out)
Here is the client's configuration:
{
"Enabled": true,
"EnableLocalLogin": false,
"ClientId": "backOffice.mvc",
"ClientName": "BackOffice client",
"ClientSecrets": [
{
"Value": "xxx"
}
],
"AllowedGrantTypes": [
"hybrid"
],
"AllowedScopes": [
"openid",
"offline_access",
"profile"
],
"RedirectUris": [
"http://localhost:5098/signin-oidc"
],
"PostLogoutRedirectUris": [
"http://localhost:5098/"
],
"RequireConsent": false,
"AllowOfflineAccess": true
}
And the provider settings:
.AddOpenIdConnect("Google", "Google", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ForwardSignOut = IdentityServerConstants.DefaultCookieAuthenticationScheme;
options.Authority = "https://accounts.google.com/";
options.ClientId = Configuration["GoogleClientId"];
options.CallbackPath = "/signin-google";
options.Scope.Add("email");
})
Thanks a lot for any help! And please let me know if you need more informations :)
Unfortunately Google does not advertise an end_session endpoint via https://accounts.google.com/.well-known/openid-configuration so front-channel sign out is not an option.
However you may be able to provide an additional prompt=login parameter in the authorize endpoint request in an attempt to force interactive authentication. You can enforce this in your client by checking that the auth_time claim is suitably recent.
My web application is using Single Sign On (SSO) service from IBM Bluemix. This is the credentials info of my SSO service:
{
"SingleSignOn": [
{
"credentials": {
"secret": "MYSECRET",
"tokenEndpointUrl": "https://adminwebsso-jjjfvxi6wy-cq17.iam.ibmcloud.com/idaas/oidc/endpoint/default/token",
"authorizationEndpointUrl": "https://adminwebsso-jjjfvxi6wy-cq17.iam.ibmcloud.com/idaas/oidc/endpoint/default/authorize",
"issuerIdentifier": "adminwebsso-jjjfvxi6wy-cq17.iam.ibmcloud.com",
"clientId": "MYCLIENTID",
"serverSupportedScope": [
"openid"
]
},
"syslog_drain_url": null,
"volume_mounts": [],
"label": "SingleSignOn",
"provider": null,
"plan": "professional",
"name": "VA-Admin-Console-R1-SSO",
"tags": [
"security",
"ibm_created",
"ibm_dedicated_public"
]
}
]
}
From my Application, I redirect to Login page of IBM like URL:
https://adminwebsso-jjjfvxi6wy-cq17.iam.ibmcloud.com/idaas/oidc/endpoint/default/authorize?response_type=code&client_id=MYCLIENTID&redirect_uri=http://localhost/callbackā»ope=openid%20openid
After login success IBM redirect to my web application, I can get parameter "code" from callback URL (http://localhost/callback?scope=openid&code=bngM6aV5cYHAvhv7wLAM5QSWFDARn7).
From there, I use the "code" to to get user profile. I have try to use AJAX to get user profile:
var settings = {
"async": true,
"crossDomain": true,
"url": "https://idaas.ng.bluemix.net/sps/oauth20sp/oauth20/token",
"method": "POST",
"headers": {
"content-type": "application/x-www-form-urlencoded",
"authorization": "Basic RXhhbXBsZV9BcHBJRDpWUFlndEdXRlRvYVpZSUNTRzhJeVZFV000bUZicGpsU2t4RlRRbzlySkRGZDdzckc=",
"cache-control": "no-cache"
},
"data": {
"client_secret": "MYSECRET",
"grant_type": "authorization_code",
"redirect_uri": "http://localhost/callback",
"code": "bngM6aV5cYHAvhv7wLAM5QSWFDARn7",
"client_id": "MYCLIENTID"
}
}
$.ajax(settings).done(function (response) {
console.log(response);
});
From ajax post above, I have tried to use the "code" from callback, but I've got an error message:
500 Error: Failed to establish a backside connection
I've got stuck here and don't know how to get user profile from SSO.
With in the SSO Service there are some inbuilt macros that can be used to get the user name, for more information please see.
https://console.bluemix.net/docs/services/SingleSignOn/customizing_pages.html#customizing_pages
#USERNAME# The user ID of the authenticated user.