Identity server 4 reference token with MVC - access-token

I am using Identity server 4(with entity-framework for configs) and defining a MVC client with reference token (AccessTokenType=1). I can login to IS4 by using the client and defined user and get access token (reference type). I know that this token does not contains claims but I have all claims in Security.Claims.ClaimPrincipal. Is it getting claims by doing behind the scene request to IS4?
I have 2 main issues:
1) I set the access token life time to 10 mins for MVC client, and cookie is valid for 450 hours. I expect that after 10 mins user redirected to login page on IS4 as access token is expired but it is not happening
2) Also when I remove PersistedGrants from database, still I am logged in and can see MVC client, Why?
Should I do anything in middleware on MVC client to check access token by using reference token?
I need this for forcing user to login on all logged in clients again.
this my MVC setting:
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
AutomaticAuthenticate = true,
ExpireTimeSpan = TimeSpan.FromHours(750),
AutomaticChallenge = true
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.UseOpenIdConnectAuthentication(
new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:7010",
RequireHttpsMetadata = false,
ClientId = "MVC_Client",
ClientSecret = "MVC_Client",
ResponseType = "code id_token",
Scope =
{
Common.Constants.IdentityManagement.OpenIdScopeName,
Common.Constants.IdentityManagement.ProfileScopeName,
Common.Constants.IdentityManagement.EmailScopeName,
},
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true,
TokenValidationParameters =
new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role
},
Events = new OpenIdConnectEvents()
{
OnTicketReceived = OnTicketReceived
}
});
The client has two grant types: hybrid,client_credentials
And this is client properties in databse:
[AbsoluteRefreshTokenLifetime]: 60
[AccessTokenLifetime]: 60
[AccessTokenType]: 1
[AllowAccessTokensViaBrowser]: False
[AllowOfflineAccess]: True
[AllowPlainTextPkce]: False
[AllowRememberConsent]: True
[AlwaysIncludeUserClaimsInIdToken]: False
[AlwaysSendClientClaims]: False
[AuthorizationCodeLifetime]: 60
[ClientId]: MVC_Client
[ClientName]: MVC_Client
[ClientUri]: NULL
[EnableLocalLogin]: True
[Enabled]: True
[IdentityTokenLifetime]: 60
[IncludeJwtId]: False
[LogoUri]: NULL
[LogoutSessionRequired]: True
[LogoutUri]: NULL
[PrefixClientClaims]: True
[ProtocolType]: oidc
[RefreshTokenExpiration]: 60
[RefreshTokenUsage]: 1
[RequireClientSecret]: True
[RequireConsent]: False
[RequirePkce]: False
[SlidingRefreshTokenLifetime]: 60
[UpdateAccessTokenClaimsOnRefresh]: False

I've solved the similar issue and I set the life time of the cookie according the life time of access token and after refreshing of access token it will be renew the cookie.
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
...
SaveTokens = true,
UseTokenLifetime = false,
Events = new OpenIdConnectEvents()
{
OnTicketReceived = n => OnTicketReceived(n)
}
});
private Task OnTicketReceived(TicketReceivedContext n)
{
var accessTokenExpiresAt = n.Properties.Items[".Token.expires_at"];
n.Properties.ExpiresUtc = DateTimeOffset.Parse(accessTokenExpiresAt);
return Task.FromResult(0);
}
Another way how you can manage the cookie is used the following method OnValidatePrincipal:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = LastChangedValidator.ValidateAsync
}
});
public static class LastChangedValidator
{
public static async Task ValidateAsync(CookieValidatePrincipalContext context)
{
// Check if user is still valid
if (!isUserValid)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
}
}
}
The following code is from here: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?tabs=aspnetcore1x

Thanks Jenan.
Set cookie expiry to token expiry works.
Another issue I have is that when I remove the reference tokens from PersistedGrants and after 10 mins (access token lifetime) the cookie is expired and I expect that user get redirected to login page. But MVC client goes to IS and IS creates new reference token without authentication. How it is possible?
In the IdentityServer log I see this:
[Microsoft.AspNetCore.Hosting.Internal.WebHost] Request starting HTTP/1.1 GET http://localhost:7010/connect/authorize?client_id=IdentityManagement&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fsignin-oidc&response_type=code%20id_token&scope=openid%20profile%20email%20Roles%20IdentityManagement%20IdentityUsers&response_mode=form_post&nonce=636452284146749770.ZTk3YzFiM2QtYTQxMi00MGI3LWJjMGEtMWFkOGRhYTE0ZjE3NDhjZGE3MjMtYTczNS00Y2ZkLThhOTctNzAxYmM4NTY4MjE5&state=CfDJ8HzK9L_BsbZJtObgOKdlRawJPSBTZc1UETnT9osu2OIOojB6vxT7t7GjIBO2nf2TYngPk3u8EcDMk8o_dVUvj8VTaEQf0s1DvTUwaxZn93_TKv1waoFukeEBFwaSB1yWTbNq62dyYkLc6_fkiW4r16BwFyKpVEvaMmh2NGLUmfFiQ-7qj6f4VyR3pM0ydd7Ah8Vs6BIfXlyqtQJ4Ak4sR1jrcGO9-ViTWCFe2YN0M9-OWFluiFQOylh4quzwseYjjOgY0ruVCwK7Lw1pvVMewnn_f2uiXk7QXBz7TMYcp8kylbdgL5Vx0fSBrB67nKSER5m-gjPXNIky6FrBPSouqzw
[Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware] HttpContext.User merged via AutomaticAuthentication from authenticationScheme: "idsrv".
[Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware] AuthenticationScheme: "idsrv" was successfully authenticated.
[IdentityServer4.Hosting.IdentityServerMiddleware] Invoking IdentityServer endpoint: "IdentityServer4.Endpoints.AuthorizeEndpoint" for "/connect/authorize"
[Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware] AuthenticationScheme: "idsrv" was successfully authenticated.
Why IdentityServer creates new reference token without authentication. The previous reference token is removed from PersistedGrants.
I am doing this for forcing user to login again in case of emergency (losing device,...)

Correct, I used this for set the IS cookie:
var props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddSeconds(120)
};
await HttpContext.Authentication.SignInAsync(userCredential.User.Id.ToString(), userCredential.User.UserName, props);
But I do not want to set it the same as client cookie expiry as it affects SSO. Also if I set the IS cookie greater than client cookie the other issue happens (as IS cookie is valid IS creates new reference token without authentication).
Now I am confused!

Related

JWT Token Expiration time failing .net core [duplicate]

This question already has answers here:
JWT Token authentication, expired tokens still working, .net core Web Api
(4 answers)
Closed 1 year ago.
I am trying to implement Token Based Authentication through refresh tokens and JWT in .NET Core 2.1.
This is how I am implementing the JWT Token:
Startup.cs
services.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = Configuration["Jwt:Site"],
ValidIssuer = Configuration["Jwt:Site"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SigningKey"]))
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
Token Generation:
var jwt = new JwtSecurityToken(
issuer: _configuration["Jwt:Site"],
audience: _configuration["Jwt:Site"],
expires: DateTime.UtcNow.AddMinutes(1),
signingCredentials: new SigningCredentials(signinKey, SecurityAlgorithms.HmacSha256)
);
return new TokenReturnViewModel()
{
token = new JwtSecurityTokenHandler().WriteToken(jwt),
expiration = jwt.ValidTo,
currentTime = DateTime.UtcNow
};
I am getting he correct values in Response.
But after a minute I set the same token for authorization in Postman and it works.
If the token has expired it shouldn't.
I am using bearer tokens as authentication.
What am I doing wrong? Need direction.
There is a token validation parameter called ClockSkew, it gets or sets the clock skew to apply when validating a time. The default value of ClockSkew is 5 minutes. That means if you haven't set it, your token will be still valid for up to 5 minutes.
If you want to expire your token on the exact time; you'd need to set ClockSkew to zero as follows,
options.TokenValidationParameters = new TokenValidationParameters()
{
//other settings
ClockSkew = TimeSpan.Zero
};
Another way, create custom AuthorizationFilter and check it manually.
var principal = ApiTokenHelper.GetPrincipalFromToken(token);
var expClaim = principal.Claims.First(x => x.Type == "exp").Value;
var tokenExpiryTime = Convert.ToDouble(expClaim).UnixTimeStampToDateTime();
if (tokenExpiryTime < DateTime.UtcNow)
{
//return token expired
}
Here, GetPrincipalFromToken is a custom method of the ApiTokenHelper class, and it will return the ClaimsPrincipal value that you've stored while issuing a token.

Adding Claims to Identity from my WebApi

I am currently developing a Web Application that contains a Angular4 UI working with IdentitServer4 and a WebApi dotnetcore application.
We have the application authentication mechanism working with IDS but we now want to limit parts of our Angular application based on what permissions a user has granted them. This information is stored behind our WebApi. These permissions would also be used to secure our WebApi from stopping users doing particular actions if they aren't allowed i.e. EditUsers.
the problem I am facing is that ideally after being authentication by IdentityServer I would like the Angular application to fetch the list of allowed
permissions and from there they send those up to the WebApi as part of their claims. The reason for this is that I don't want to have to query the database
on each Api Call if I can help it just to see if a particular user has access to a particular Controller action.
Is there anyway that I can set these claims so that subsequent calls to the API contains these and from there I can just check the claim information on the User Claim Identity
to verify access to a resource?
You can add scope to your WebApi (official docs) for example
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "https://demo.identityserver.io",
ApiName = "api1",
AllowedScopes = { "api1.read", "api1.write" }
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
And you can add claims to the client application as:
var mvcClient = new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
ClientUri = "http://identityserver.io",
AllowedGrantTypes = GrantTypes.Hybrid,
AllowOfflineAccess = true,
ClientSecrets = { new Secret("secret".Sha256()) },
RedirectUris = { "http://localhost:5002/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5002/" },
LogoutUri = "http://localhost:5002/signout-oidc",
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"api1", "api2.read"
},
};
This is application base, for assigning permissions per user, you can define roles in your scopes for the user and can then decorate your controller or methods with that Role for ex:
For admin: new Claim("role","Admin")
For guestuser: new Claim("role","guest")
[HttpGet]
[Authorize(Roles = "Admin")]
public IActionResult Edit()
{
//whatever
}
[Authorize(Roles = "Guest")]
[HttpGet]
public IActionResult View()
{
//whatever
}

401 response when using Xamarin and IdentityModel to WebAPI secured by IdentityServer 3

I am having an identical problem to this but their solution doesn't work for me.
I'm calling a WebAPI method (.Net 4.5.2) and the project has a reference to IdentityModel 1.13.1 and it is protected using IdentityServer 3 with the following code in the startup class -
JwtSecurityTokenHandler.InboundClaimTypeMap.Clear();
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44305/core/",
RequiredScopes = new[] { "read", "write" },
// client credentials for the introspection endpoint
ClientId = "clientcredentials.client",
ClientSecret = "secret"
});
The clients configuration in the IdentityServer startup includes the following client definition -
new Client
{
ClientName = "Mobile Api Client",
Enabled = true,
ClientId = "clientcredentials.client",
Flow = Flows.ClientCredentials,
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256()),
new Secret
{
Value = "[valid thumbprint]",
Type = "X509Thumbprint",
Description = "Client Certificate"
},
},
AllowedScopes = new List<string>
{
"read",
"write"
},
Claims = new List<Claim>
{
new Claim("location", "datacenter")
}
}
And in the Xamarin client (which also uses IdentityModel 1.13.1) ...
var token = IdentityServerClient.RequestClientToken(); // this returns a valid bearer token
TokenResultLabel.Text = token.Raw;
HttpClient apiClient = new HttpClient();
apiClient.SetBearerToken(token.AccessToken);
var result = await apiClient.GetStringAsync("[valid api URL]");
ApiResultLabel.Text = result;
I've tried it with IdentityModel 2.0 (latest compatible version), 1.13.1 (the version mentioned in the referenced question, and 1.9.2 (the version in the IdentityServer 3 samples)
Any help would be greatly appreciated
While the configuration allows the client to request both the read and write scopes, do you explicitly specify when you request the access token that you wish to get one containing these 2 scopes? This should happen in your IdentityServerClient.RequestClientToken method.
Allowing a client to request scopes doesn't mean these scopes will automatically be included in access tokens returned by IdentityServer.

Web app and web api authentication in same application

I have a web app MVC,using auth0 owin regular web app cookie based authentication.
This web app also has webapis which is used internally in the application. However i have a requirement to call this webapis from outside the application. So i created a restclient and tried to implement jwtbearerauthentication in application (but cookie based on authentication still in place).
Now when i call the webapi from other application it validates the bearer token gives no error however it redirects to login page due to cookie based authentication.
startup file:
public partial class Startup
{
private IPlatform platform;
public void ConfigureAuth(IAppBuilder app, IPlatform p, IContainer container)
{
platform = p;
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = System.TimeSpan.FromDays(2),
SlidingExpiration = true
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
var provider = new Auth0.Owin.Auth0AuthenticationProvider
{
OnReturnEndpoint = (context) =>
{
// xsrf validation
if (context.Request.Query["state"] != null && context.Request.Query["state"].Contains("xsrf="))
{
var state = HttpUtility.ParseQueryString(context.Request.Query["state"]);
AntiForgery.Validate(context.Request.Cookies["__RequestVerificationToken"], state["xsrf"]);
}
return System.Threading.Tasks.Task.FromResult(0);
},
OnAuthenticated = (context) =>
{
var identity = context.Identity;
//Add claims
var authenticationManager = container.Resolve<IAuthenticationManager>();
authenticationManager.AddClaims(identity);
if (context.Request.Query["state"] != null)
{
authenticationManager.AddReturnUrlInClaims(identity, context.Request.Query["state"]);
}
return System.Threading.Tasks.Task.FromResult(0);
}
};
var issuer = "https://" + ConfigurationManager.AppSettings["auth0:Domain"] + "/";
var audience = ConfigurationManager.AppSettings["auth0:ClientId"];
var secret = TextEncodings.Base64.Encode(TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["auth0:ClientSecret"]));
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
}
});
app.UseAuth0Authentication(
clientId: platform.ServerRole.GetConfigurationSettingValue("auth0:ClientId"),
clientSecret: platform.ServerRole.GetConfigurationSettingValue("auth0:ClientSecret"),
domain: platform.ServerRole.GetConfigurationSettingValue("auth0:Domain"),
provider: provider);
}
}
webapiconfig file:
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new {id = RouteParameter.Optional});
config.Filters.Add(new AuthorizeAttribute());
ODataConfig.Setup(config);
var clientID = WebConfigurationManager.AppSettings["auth0:ClientId"];
var clientSecret = WebConfigurationManager.AppSettings["auth0:ClientSecret"];
config.MessageHandlers.Add(new JsonWebTokenValidationHandler()
{
Audience = clientID,
SymmetricKey = clientSecret
});
}
Currently creating the jwt token from below code and posting using postman in header just to check if it works.. but redirects to login page.
string token = JWT.Encode(payload, secretKey, JwsAlgorithm.HS256);
I suspect what's happening is that your call to the API has a bearer token which fails validation (or there is no Authorize token at all), your API controller has an Authorize attribute, which, since there is no valid ClaimsPrincipal on the call throws 401. Auth0AuthenticationProvider picks that and assumes the call was to UI so redirects for user authentication. You may want to add an override in the Oauth0Provider to trap OnRedirectToIdP (or something like that), inspect the request and if it is to API, abot further handling and return Unauthorized.
Remove any [Authorize] from your API and see whether it works then. Also make sure your startup does not require Authorize for all controllers.
You may want to remove the authn part of your code (cookie and Oauth2Provider) and see whether you are getting to the API then.
A few years late i know, but i recently came across the same requirement in a project, and found this sample put together by a dev at Auth0.
https://github.com/auth0-samples/aspnet-core-mvc-plus-webapi
The example in the link allows for cookie authentication OR token authentication for the API endpoints.
The key takeaway for me was using attributes on your routes to tell the pipline what authentication mechanism to use. In my case i wanted cookie authentication for the UI and token authentication for the endpoints. i had no requirement to use both for any single area of the project.
controller:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpGet]
[Route("api")]
public string TestAuth()
{
return "All good " + this.User.FindFirst(ClaimTypes.NameIdentifier).Value + ". You only get this message if you are authenticated.";
}

How to add claims to access token get from IdentityServer3 using resource owner flow with javascript client

I use the resource owner flow with IdentityServer3 and send get token request to identity server token endpoint with username and password in javascript as below:
function getToken() {
var uid = document.getElementById("username").value;
var pwd = document.getElementById("password").value;
var xhr = new XMLHttpRequest();
xhr.onload = function (e) {
console.log(xhr.status);
console.log(xhr.response);
var response_data = JSON.parse(xhr.response);
if (xhr.status === 200 && response_data.access_token) {
getUserInfo(response_data.access_token);
getValue(response_data.access_token);
}
}
xhr.open("POST", tokenUrl);
var data = {
username: uid,
password: pwd,
grant_type: "password",
scope: "openid profile roles",
client_id: 'client_id'
};
var body = "";
for (var key in data) {
if (body.length) {
body += "&";
}
body += key + "=";
body += encodeURIComponent(data[key]);
}
xhr.setRequestHeader("Authorization", "Basic " + btoa(client_id + ":" + client_secret));
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(body);
}
The access token is returned from identity server and user is authenticated. Then I use this token to send request to my Web Api.
The problem is that when I check if the user is assigned a role, I find the claim doesn't exist.
[Authorize]
// GET api/values
public IEnumerable<string> Get()
{
var id = RequestContext.Principal as ClaimsPrincipal;
bool geek = id.HasClaim("role", "Geek"); // false here
bool asset_mgr = id.HasClaim("role", "asset_manager"); // false here
return new string[] { "value1", "value2" };
}
Here is how the client is defined in identity server.
new Client
{
ClientName = "Client",
ClientId = "client_id",
Flow = Flows.ResourceOwner,
RequireConsent = false,
AllowRememberConsent = false,
AllowedScopes = new List<string>
{
"openid",
"profile",
"roles",
"sampleApi"
},
AbsoluteRefreshTokenLifetime = 86400,
SlidingRefreshTokenLifetime = 43200,
RefreshTokenUsage = TokenUsage.OneTimeOnly,
RefreshTokenExpiration = TokenExpiration.Sliding,
ClientSecrets = new List<Secret>
{
new Secret("4C701024-0770-4794-B93D-52B5EB6487A0".Sha256())
},
},
and this is how the user is defined:
new InMemoryUser
{
Username = "bob",
Password = "secret",
Subject = "1",
Claims = new[]
{
new Claim(Constants.ClaimTypes.GivenName, "Bob"),
new Claim(Constants.ClaimTypes.FamilyName, "Smith"),
new Claim(Constants.ClaimTypes.Role, "Geek"),
new Claim(Constants.ClaimTypes.Role, "Foo")
}
}
How can I add claims to the access_token in this case? Thanks a lot!
I have just spent a while figuring this out myself. #leastprivilege's comment on Yang's answer had the clue, this answer is just expanding on it.
It's all down to how the oAuth and OIDC specs evolved, it's not an artefact of IdentityServer (which is awesome).
Firstly, here is a fairly decent discussion of the differences between identity tokens and access tokens: https://github.com/IdentityServer/IdentityServer3/issues/2015 which is worth a read.
With Resource Owner flow, like you are doing, you will always get an Access Token. By default and per the spec, you shouldn't include claims in that token (see the above link for why). But, in practice, it is very nice when you can; it saves you extra effort on both client and server.
What Leastprivilege is referring to is that you need to create a scope, something like this:
new Scope
{
Name = "member",
DisplayName = "member",
Type = ScopeType.Resource,
Claims = new List<ScopeClaim>
{
new ScopeClaim("role"),
new ScopeClaim(Constants.ClaimTypes.Name),
new ScopeClaim(Constants.ClaimTypes.Email)
},
IncludeAllClaimsForUser = true
}
And then you need to request that scope when you ask for the token. I.e. your line
scope: "openid profile roles", should change to scope: "member", (well, I say that - scopes play a dual role here, as far as I can see - they are also a form of control, i.e. the client is asking for certain scopes and can be rejected if it is not allowed those but that is another topic).
Note the important line that eluded me for a while, which is Type = ScopeType.Resource (because Access Tokens are about controlling access to resources). This means it will apply to Access Tokens and the specified claims will be included in the token (I think, possibly, against spec but wonderfully).
Finally, in my example I have included both some specific claims as well as IncludeAllClaimsForUser which is obviously silly, but just wanted to show you some options.
I find I can achieve this by replacing the default IClaimsProvider of IdentityServerServiceFactory.
The cusomized IClaimsProvider is as below:
public class MyClaimsProvider : DefaultClaimsProvider
{
public MaccapClaimsProvider(IUserService users) : base(users)
{
}
public override Task<IEnumerable<Claim>> GetAccessTokenClaimsAsync(ClaimsPrincipal subject, Client client, IEnumerable<Scope> scopes, ValidatedRequest request)
{
var baseclaims = base.GetAccessTokenClaimsAsync(subject, client, scopes, request);
var claims = new List<Claim>();
if (subject.Identity.Name == "bob")
{
claims.Add(new Claim("role", "super_user"));
claims.Add(new Claim("role", "asset_manager"));
}
claims.AddRange(baseclaims.Result);
return Task.FromResult(claims.AsEnumerable());
}
public override Task<IEnumerable<Claim>> GetIdentityTokenClaimsAsync(ClaimsPrincipal subject, Client client, IEnumerable<Scope> scopes, bool includeAllIdentityClaims, ValidatedRequest request)
{
var rst = base.GetIdentityTokenClaimsAsync(subject, client, scopes, includeAllIdentityClaims, request);
return rst;
}
}
Then, replace the IClaimsProvider like this:
// custom claims provider
factory.ClaimsProvider = new Registration<IClaimsProvider>(typeof(MyClaimsProvider));
The result is that, when the request for access token is sent to token endpoint the claims are added to the access_token.
Not only that I tried other methods, I tried all possible combinations of scopes etc. All I could read in the access token was "scope", "scope name", for Resource Flow there were no claims I have added period.
I had to do all this
Add custom UserServiceBase and override AuthenticateLocalAsync since I have username/password there and I need both to fetch things from the database
Add claims that I need in the same function (this on itself will not add claim to Access Token, however you will able to read them in various ClaimsPrincipal parameters around)
Add custom DefaultClaimsProvider and override GetAccessTokenClaimsAsync where ClaimsPrincipal subject contains the claims I previously set, I just take them out and put again into ølist of claims for the result.
I guess this last step might be done overriding GetProfileDataAsync in the custom UserServiceBase, but the above just worked so I did not want to bother.
The general problem is not how to set claims, it is where you populate them. You have to override something somewhere.
This here worked for me since I needed data from a database, someone else should populate claims elsewhere. But they are not going to magically appear just because you nicely set Scopes and Claims Identity Server configurations.
Most of the answers say not a word about where to set the claim values properly. In each particular override you have done, the passed parameters, when they have claims, in the function are attached to identity or access token.
Just take care of that and all will be fine.

Resources