IdentityServer4 - access token only contains sub claim - identityserver4

I've finally been able to get an access token from IdentityServer, and use it to retrieve the UserInfo.
However it seems that the access token I'm getting from IdentityServer only contains the sub claim.
Here is an example token
eyJhbGciOiJSUzI1NiIsImtpZCI6IjVCOTBDN0JBNkExMjI2RjEyMEU0QzJGOEQzMjIwMzAxIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2MzcwNTI5MDIsImV4cCI6MTYzNzEzOTMwMiwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NDQzNTkiLCJhdWQiOiJ3ZWF0aGVyZm9yZWNhc3RzIiwiY2xpZW50X2lkIjoiU3NvQXBwbGljYXRpb25DbGllbnQiLCJjZW50cmFsLXRoZWNsaWVudCI6IlRoZSBTU08gY2xpZW50Iiwic3ViIjoiYTUxYjBkZWMtODQyYS00ZWMyLTgwMGEtMzRmYWQyNTRjZTBlIiwiYXV0aF90aW1lIjoxNjM3MDUyMDcyLCJpZHAiOiJsb2NhbCIsImp0aSI6IkQ3Rjc2MzgwQzNERDkzMzVERDVFMTE1NjE4MDkzNEUwIiwic2lkIjoiOEY1OUZFMjRDNkY1NTM2ODhEQzVCMTM5QjNFQUU1MTMiLCJpYXQiOjE2MzcwNTI5MDIsInNjb3BlIjpbIm9wZW5pZCIsInByb2ZpbGUiLCJlbWFpbCIsIndlYXRoZXJmb3JlY2FzdHMucmVhZCIsIndlYXRoZXJmb3JlY2FzdHMud3JpdGUiXSwiYW1yIjpbInB3ZCJdfQ.MiDmgc7AzbVpogbp8ID3WvJ0eo4a30_taxR9EI9ylyJASSdOiNSsk-sGuW-YnJIzf668EQGkTym6FMIvOyTxem9DxxIs8nI_rboHLvuvj4e7CtJeELwbZyraZtAxjVjm9tn0BVRZxuskzb6XSq4xGrt2ag_E0Re5MeQOjtyL0EeMS5md5IEywfD7ThH7pIu8SofFvV5tAYbwO-OPd5YyqpPGKXslRtFlyc7lj9faQh-e2CRMql5rSwhJRqCiaIaLxvXk8ZwISfdhmuyzHA88xrzXkqTK_RElhq4PY_GqpRe64nMvIBrkSeoOGLzlQNE9wa58UypZFFV4l8Cpy3_P2Q
Here is the decoded token
I should be able to use this token to call the /connect/userinfo endpoint
And I can see this in the Output window:
IdentityServer4.Hosting.EndpointRouter: Debug: Request path /connect/userinfo matched to endpoint type Userinfo
IdentityServer4.Hosting.EndpointRouter: Debug: Endpoint enabled: Userinfo, successfully created handler: IdentityServer4.Endpoints.UserInfoEndpoint
IdentityServer4.Hosting.IdentityServerMiddleware: Information: Invoking IdentityServer endpoint: IdentityServer4.Endpoints.UserInfoEndpoint for /connect/userinfo
IdentityServer4.Endpoints.UserInfoEndpoint: Debug: Start userinfo request
IdentityServer4.Validation.BearerTokenUsageValidator: Debug: Bearer token found in header
IdentityServer4.Endpoints.UserInfoEndpoint: Trace: Calling into userinfo request validator: IdentityServer4.Validation.UserInfoRequestValidator
IdentityServer4.Validation.TokenValidator: Trace: Start access token validation
IdentityServer4.EntityFramework.Stores.ClientStore: Debug: SsoApplicationClient found in database: True
IdentityServer4.Stores.ValidatingClientStore: Trace: Calling into client configuration validator: IdentityServer4.Validation.DefaultClientConfigurationValidator
IdentityServer4.Stores.ValidatingClientStore: Debug: client configuration validation for client SsoApplicationClient succeeded.
IdentityServer4.EntityFramework.Stores.ClientStore: Debug: SsoApplicationClient found in database: True
IdentityServer4.Stores.ValidatingClientStore: Trace: Calling into client configuration validator: IdentityServer4.Validation.DefaultClientConfigurationValidator
IdentityServer4.Stores.ValidatingClientStore: Debug: client configuration validation for client SsoApplicationClient succeeded.
IdentityServer4.Validation.TokenValidator: Debug: Calling into custom token validator: IdentityServer4.Validation.DefaultCustomTokenValidator
IdentityServer4.Validation.TokenValidator: Debug: Token validation success
{
"ValidateLifetime": true,
"AccessTokenType": "Jwt",
"ExpectedScope": "openid",
"JwtId": "2FB8F8A941528DAF18D8C523BCC9A770",
"Claims": {
"nbf": 1637062004,
"exp": 1637148404,
"iss": "https://localhost:44359",
"aud": "weatherforecasts",
"client_id": "SsoApplicationClient",
"central-theclient": "The SSO client",
"sub": "959c9bfa-ed30-4638-9986-63cf1589eff8",
"auth_time": 1637060908,
"idp": "local",
"jti": "2FB8F8A941528DAF18D8C523BCC9A770",
"sid": "75C294FC15A544FE60E361B495EE5BCA",
"iat": 1637062004,
"scope": [
"openid",
"profile",
"email",
"weatherforecasts.read",
"weatherforecasts.write"
],
"amr": "pwd"
}
}
IdentityServer4.Endpoints.UserInfoEndpoint: Trace: Calling into userinfo response generator: IdentityServer4.ResponseHandling.UserInfoResponseGenerator
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Creating userinfo response
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Scopes in access token: openid profile email weatherforecasts.read weatherforecasts.write
IdentityServer4.EntityFramework.Stores.ResourceStore: Debug: Found openid, profile, email identity scopes in database
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Requested claim types:
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Information: Profile service returned the following claim types:
IdentityServer4.Endpoints.UserInfoEndpoint: Debug: End userinfo request
You can even see my custom dedicated client claims being appended during the token validation.
But yet only the sub claim is returned from the identityserver userinfo endpoint...
How can I fix this?
Edit
I added the ProfileService, and it's being called. But as Dejan already mentioned, RequestedClaimTypes is empty. However I updated the database by adding
[{"type":"email","identityResourceId":1003},{"type":"sub","identityResourceId":1003}]
records to the IdentityResourceClaim table. In the output window I can now see that the email and sub claims are requested:
IdentityServer4.Validation.TokenValidator: Debug: Token validation success
{
"ValidateLifetime": true,
"AccessTokenType": "Jwt",
"ExpectedScope": "openid",
"JwtId": "C37AF164BF3A7DE6A28FA7538683248F",
"Claims": {
"nbf": 1637069285,
"exp": 1637155685,
"iss": "https://localhost:44359",
"aud": "weatherforecasts",
"client_id": "SsoApplicationClient",
"central-theclient": "The SSO client",
"sub": "959c9bfa-ed30-4638-9986-63cf1589eff8",
"auth_time": 1637067659,
"idp": "local",
"email": "pieterjan#example.com",
"name": "Pieterjan",
"jti": "C37AF164BF3A7DE6A28FA7538683248F",
"sid": "BBFA9FD0A06824FA4E982DB1D3669A86",
"iat": 1637069285,
"scope": [
"openid",
"profile",
"email",
"weatherforecasts.read",
"weatherforecasts.write"
],
"amr": "pwd"
}
}
IdentityServer4.Endpoints.UserInfoEndpoint: Trace: Calling into userinfo response generator: IdentityServer4.ResponseHandling.UserInfoResponseGenerator
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Creating userinfo response
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Scopes in access token: openid profile email weatherforecasts.read weatherforecasts.write
IdentityServer4.EntityFramework.Stores.ResourceStore: Debug: Found openid, profile, email identity scopes in database
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Debug: Requested claim types: email sub
IdentityServer4.ResponseHandling.UserInfoResponseGenerator: Information: Profile service returned the following claim types: email name
IdentityServer4.Endpoints.UserInfoEndpoint: Debug: End userinfo request
IdentityServer4.Hosting.IdentityServerMiddleware: Trace: Invoking result: IdentityServer4.Endpoints.Results.UserInfoResult
The code finally enters the ExternalLoginCallback, but the await signInManager.GetExternalLoginInfoAsync() returns null now.
The response from https://localhost:44359/connect/userinfo is
{
"email": "pieterjan#example.com",
"name": "Pieterjan",
"sub": "959c9bfa-ed30-4638-9986-63cf1589eff8"
}

The userinfo endpoint calls GetProfileDataAsync from IdentityServer4.Services.IProfileService to obtain the requested claim values. So the simple solution would be to implement that service.
Assuming you have your user manager defined as IMyUserManager and that it has the methods referenced here (GetClaimsForUser and IsActive), the simplified implementation might look like this:
public class MyProfileService : IProfileService
{
private readonly IMyUserManager userManager;
public MyProfileService(IMyUserManager usermanager)
{
this.userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// context.RequestedClaimTypes will contain the claims you requested when invoking the token endpoint
var myClaims = await userManager.GetClaimsForUser(context.Subject, context.RequestedClaimTypes);
context.IssuedClaims = myClaims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = await userManager.IsActive(context.Subject);
}
}
You would then need to configure IdentityServer4 to use this implementation, by calling AddProfileService at startup like this:
services.AddIdentityServer()
// ...
.AddProfileService<MyProfileService>();
I should also add that, in order for your wanted claim to be present in context.RequestedClaimTypes, it needs to be associated with the scopes you directly request when making a call to token endpoint.
At startup, when configuring IdentityServer4, make sure the identity resources you supplied to your AddIdentityResources call contains the required claim.

Related

Token refresh store to allow refresh_token

I have IS4 configured and all works fine except refresh_token where I get BAD REQUESTS (400) back all the time.
I have implemented the PersistedGrantStore to store the tokens since reading the logs I could see:
2020/09/01 17:57:25.589|TRACE|Processing token request. |IdentityServer4.Endpoints.TokenEndpoint|
2020/09/01 17:57:25.616|DEBUG|Start token request. |IdentityServer4.Endpoints.TokenEndpoint|
2020/09/01 17:57:25.624|DEBUG|Start client validation |IdentityServer4.Validation.ClientSecretValidator|
2020/09/01 17:57:25.638|DEBUG|Start parsing Basic Authentication secret |IdentityServer4.Validation.BasicAuthenticationSecretParser|
2020/09/01 17:57:25.638|DEBUG|Start parsing for secret in post body |IdentityServer4.Validation.PostBodySecretParser|
2020/09/01 17:57:25.701|DEBUG|client id without secret found |IdentityServer4.Validation.PostBodySecretParser|
2020/09/01 17:57:25.701|DEBUG|Parser found secret: PostBodySecretParser |IdentityServer4.Validation.SecretParser|type=PostBodySecretParser
2020/09/01 17:57:25.720|DEBUG|Secret id found: autosmoraga_transportes_mobile_app |IdentityServer4.Validation.SecretParser|id=autosmoraga_transportes_mobile_app
2020/09/01 17:57:25.734|TRACE|Calling into client configuration validator: IdentityServer4.Validation.DefaultClientConfigurationValidator |IdentityServer4.Stores.ValidatingClientStore|validatorType=IdentityServer4.Validation.DefaultClientConfigurationValidator
2020/09/01 17:57:25.749|DEBUG|client configuration validation for client autosmoraga_transportes_mobile_app succeeded. |IdentityServer4.Stores.ValidatingClientStore|clientId=autosmoraga_transportes_mobile_app
2020/09/01 17:57:25.749|DEBUG|Public Client - skipping secret validation success |IdentityServer4.Validation.ClientSecretValidator|
2020/09/01 17:57:25.766|DEBUG|Client validation success |IdentityServer4.Validation.ClientSecretValidator|
2020/09/01 17:57:25.785|TRACE|Calling into token request validator: IdentityServer4.Validation.TokenRequestValidator |IdentityServer4.Endpoints.TokenEndpoint|type=IdentityServer4.Validation.TokenRequestValidator
2020/09/01 17:57:25.799|DEBUG|Start token request validation |IdentityServer4.Validation.TokenRequestValidator|
2020/09/01 17:57:25.825|DEBUG|Start validation of refresh token request |IdentityServer4.Validation.TokenRequestValidator|
2020/09/01 17:57:25.856|TRACE|Start refresh token validation |IdentityServer4.Validation.TokenValidator|
2020/09/01 17:57:25.928|DEBUG|refresh_token grant with value: lSWqDibFzKevkEI6KMNQyGimeK7MS7Yrjenk34XEDNQ
not found in store. |IdentityServer4.Stores.DefaultRefreshTokenStore|grantType=refresh_token, key=lSWqDibFzKevkEI6KMNQyGimeK7MS7Yrjenk34XEDNQ
2020/09/01 17:57:25.939|WARN|Invalid refresh token |IdentityServer4.Validation.TokenValidator|
2020/09/01 17:57:25.961|WARN|Refresh token validation failed. aborting, {"ClientId":"XXX", "ClientName":"XXX", "GrantType":"refresh_token", "Raw":{"client_id":"XXX","redirect_uri\n":"http:\/\/localhost:8100\/auth-callback","grant_type":"refresh_token","refresh_token":"***REDACTED***"}} |IdentityServer4.Validation.TokenRequestValidator|details={
"ClientId": "XXXX",
"ClientName": "XXX",
"GrantType": "refresh_token",
"Raw": {
"client_id": "XXXX",
"redirect_uri\n": "http://localhost:8100/auth-callback",
"grant_type": "refresh_token",
"refresh_token": "***REDACTED***"
}
}
I have registered the .AddPersistedGrantStore<PersistedGrantStore>() method however I still can see in the logs that DefaultRefreshTokenStore is being called however I see in the database I have records of type refresh_token stored. I'm not sure if I'm missing something. All works fine except the refresh token part. The client configuration is set with online access enabled:
new Client
{
ClientId = "XXXX",
ClientName = "XXX",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RequireClientSecret = false,
AllowedScopes = new List<string> { "openid", "profile", "myapi" },
RedirectUris = new List<string> {
$"{mobileBaseUrl}/auth-callback",
$"{mobileAppBaseUrl}auth-callback",
$"{mobileAppOrigin}/auth-callback",
},
PostLogoutRedirectUris = new List<string> {
$"{mobileBaseUrl}/end-session",
$"{mobileAppBaseUrl}end-session",
$"{mobileAppOrigin}/end-session",
},
AllowedCorsOrigins = new List<string> {
mobileBaseUrl,
mobileAppOrigin
},
AllowOfflineAccess = true,
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
AlwaysSendClientClaims = true,
AlwaysIncludeUserClaimsInIdToken = true,
}
so I'm not entirely sure what else to check. For reference I have the issue fully explained here: https://github.com/wi3land/ionic-appauth/issues/33

IdentityServer4: Quickstart always returns Unauthorized for API endpoint, with message "The audience 'https://localhost:5001/resources' is invalid"

Following the instructions at https://docs.identityserver.io/en/latest/quickstarts/1_client_credentials.html verbatim, I am unable to execute a client. Always returns a 401 Unauthorized.
The token generated is:
{
"alg": "RS256",
"kid": "57EDAEBEC68F3CAACE869E3FA226C0FF",
"typ": "at+jwt"
}.{
"nbf": 1593466354,
"exp": 1593469954,
"iss": "https://localhost:5001",
"aud": "https://localhost:5001/resources",
"client_id": "client",
"jti": "C76BC9CB471ED81832A56B78059421FB",
"iat": 1593466354,
"scope": [
"api1"
]
}.[Signature]
But I see no way to set the audience. :s
My Api
Startup.cs:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace api
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
// Use our IS4 implementation as the authentication source.
options.Authority = "https://localhost:5001";
options.RequireHttpsMetadata = false;
options.Audience = "api1";
});
services.AddAuthorization(options =>
{
options.AddPolicy("ApiScopePolicy", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", "api1");
});
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
MyEndpointController
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Api.Controllers
{
[Route("MyEndpoint")]
[Authorize]
public class MyEndpointController : ControllerBase
{
[HttpGet]
[Route("Get")]
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
}
My Identity Server implementation
Startup.cs
public class Startup
{
public IWebHostEnvironment Environment { get; }
public Startup(IWebHostEnvironment environment)
{
Environment = environment;
}
public void ConfigureServices(IServiceCollection services)
{
// uncomment, if you want to add an MVC-based UI
services.AddControllersWithViews();
var builder = services.AddIdentityServer(options =>
{
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryApiResources(Config.Apis)
.AddInMemoryClients(Config.Clients)
.AddJwtBearerClientAuthentication()
;
// not recommended for production - you need to store your key material somewhere secure
builder.AddDeveloperSigningCredential();
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// uncomment if you want to add MVC
app.UseStaticFiles();
app.UseRouting();
//-----------------------------------
app.UseIdentityServer();
// uncomment, if you want to add MVC
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
//-----------------------------------
}
}
Config.cs
public static class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId()
};
public static IEnumerable<ApiResource> Apis =>
new List<ApiResource>
{
new ApiResource("api1", "My API")
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope() {
Description = "An example scope",
DisplayName = "api1",
Enabled = true,
Name = "api1",
ShowInDiscoveryDocument = true,
UserClaims = new string[] {"UserClaim1", "UserClaim2"}
}
};
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client() {
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
}
};
}
And my console program:
program.cs
using System;
using System.Net.Http;
using System.Threading.Tasks;
using IdentityModel.Client;
using Newtonsoft.Json.Linq;
namespace ClientConsoleApp
{
class Program
{
static async Task Main(string[] args)
{
// discover endpoints from metadata
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001");
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
}
Console.WriteLine("============================================================================================");
Console.WriteLine("Discovery Document:");
Console.WriteLine("============================================================================================");
Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(disco));
// request token
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "client",
ClientSecret = "secret",
Scope = "api1"
});
if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
return;
}
Console.WriteLine("============================================================================================");
Console.WriteLine("Token Response:");
Console.WriteLine("============================================================================================");
Console.WriteLine(tokenResponse.Json);
const string endpoint = "https://localhost:6001/MyEndpoint/Get";
Console.WriteLine("============================================================================================");
Console.WriteLine($"Calling api endpoint {endpoint}");
Console.WriteLine("============================================================================================");
// call api
var apiClient = new HttpClient(new LoggingHandler(new HttpClientHandler()));
apiClient.SetBearerToken(tokenResponse.AccessToken);
Console.WriteLine("============================================================================================");
Console.WriteLine("Request");
Console.WriteLine("============================================================================================");
var response = await apiClient.GetAsync(endpoint);
Console.WriteLine("============================================================================================");
Console.WriteLine("Response");
Console.WriteLine("============================================================================================");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
}
}
}
The output is:
============================================================================================
Discovery Document:
============================================================================================
{"Policy":{"LoopbackAddresses":["localhost","127.0.0.1"],"Authority":"https://localhost:5001","AuthorityValidationStrategy":{},"RequireHttps":true,"AllowHttpOnLoopback":true,"ValidateIssuerName":true,"ValidateEndpoints":true,"EndpointValidationExcludeList":[],"AdditionalEndpointBaseAddresses":[],"RequireKeySet":true},"KeySet":{"Keys":[{"alg":"RS256","e":"AQAB","key_ops":[],"kid":"57EDAEBEC68F3CAACE869E3FA226C0FF","kty":"RSA","n":"oFo6iB0Kd-wzEFeR-fY12_8cF2uirsHI5FAtTAAOlAWUm5MRIPJjpXy8D4R9ZjU5750JUqcotQii8YF4DP_lN8Ro3SKFtI9HD4IazsX65ici2hhKSdAl4MEdUBRIgEdCwolQJgDOAhqls6WNqLRsh1Ify0EKI9AVKInwTbEXgCaHSsqGw8zubx8fSdQ4lgxQZGii792XYPVhFXMoom-6dVY9_7z5o5Or2sATdqaEAuLPLZLqMNVT284S9vMd4hxolIxVbuRgKQV4MZ-1mBK_C-GqjishVxdew6d_GasmRAt_2s0R4JlgZgeqzd7U2Agu5RETxpv6WUiDC9qCZnmXjQ","use":"sig","x5c":[],"KeySize":2048,"HasPrivateKey":false}]},"Issuer":"https://localhost:5001","AuthorizeEndpoint":"https://localhost:5001/connect/authorize","TokenEndpoint":"https://localhost:5001/connect/token","UserInfoEndpoint":"https://localhost:5001/connect/userinfo","IntrospectionEndpoint":"https://localhost:5001/connect/introspect","RevocationEndpoint":"https://localhost:5001/connect/revocation","DeviceAuthorizationEndpoint":"https://localhost:5001/connect/deviceauthorization","JwksUri":"https://localhost:5001/.well-known/openid-configuration/jwks","EndSessionEndpoint":"https://localhost:5001/connect/endsession","CheckSessionIframe":"https://localhost:5001/connect/checksession","RegistrationEndpoint":null,"FrontChannelLogoutSupported":true,"FrontChannelLogoutSessionSupported":true,"GrantTypesSupported":["authorization_code","client_credentials","refresh_token","implicit","urn:ietf:params:oauth:grant-type:device_code"],"CodeChallengeMethodsSupported":["plain","S256"],"ScopesSupported":["openid","api1","offline_access"],"SubjectTypesSupported":["public"],"ResponseModesSupported":["form_post","query","fragment"],"ResponseTypesSupported":["code","token","id_token","id_token token","code id_token","code token","code id_token token"],"ClaimsSupported":["sub","UserClaim1","UserClaim2"],"TokenEndpointAuthenticationMethodsSupported":["client_secret_basic","client_secret_post","private_key_jwt"],"HttpResponse":{"Version":"1.1","Content":{"Headers":[{"Key":"Content-Type","Value":["application/json; charset=UTF-8"]}]},"StatusCode":200,"ReasonPhrase":"OK","Headers":[{"Key":"Date","Value":["Mon, 29 Jun 2020 20:14:49 GMT"]},{"Key":"Server","Value":["Kestrel"]},{"Key":"Transfer-Encoding","Value":["chunked"]}],"TrailingHeaders":[],"RequestMessage":{"Address":"https://localhost:5001","ClientId":null,"ClientSecret":null,"ClientAssertion":{"Type":null,"Value":null},"ClientCredentialStyle":1,"AuthorizationHeaderStyle":0,"Parameters":{},"Version":"1.1","Content":null,"Method":{"Method":"GET"},"RequestUri":"https://localhost:5001/.well-known/openid-configuration","Headers":[{"Key":"Accept","Value":["application/json"]}],"Properties":{}},"IsSuccessStatusCode":true},"Raw":"{\"issuer\":\"https://localhost:5001\",\"jwks_uri\":\"https://localhost:5001/.well-known/openid-configuration/jwks\",\"authorization_endpoint\":\"https://localhost:5001/connect/authorize\",\"token_endpoint\":\"https://localhost:5001/connect/token\",\"userinfo_endpoint\":\"https://localhost:5001/connect/userinfo\",\"end_session_endpoint\":\"https://localhost:5001/connect/endsession\",\"check_session_iframe\":\"https://localhost:5001/connect/checksession\",\"revocation_endpoint\":\"https://localhost:5001/connect/revocation\",\"introspection_endpoint\":\"https://localhost:5001/connect/introspect\",\"device_authorization_endpoint\":\"https://localhost:5001/connect/deviceauthorization\",\"frontchannel_logout_supported\":true,\"frontchannel_logout_session_supported\":true,\"backchannel_logout_supported\":true,\"backchannel_logout_session_supported\":true,\"scopes_supported\":[\"openid\",\"api1\",\"offline_access\"],\"claims_supported\":[\"sub\",\"UserClaim1\",\"UserClaim2\"],\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\",\"implicit\",\"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\",\"private_key_jwt\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"subject_types_supported\":[\"public\"],\"code_challenge_methods_supported\":[\"plain\",\"S256\"],\"request_parameter_supported\":true}","Json":{"issuer":"https://localhost:5001","jwks_uri":"https://localhost:5001/.well-known/openid-configuration/jwks","authorization_endpoint":"https://localhost:5001/connect/authorize","token_endpoint":"https://localhost:5001/connect/token","userinfo_endpoint":"https://localhost:5001/connect/userinfo","end_session_endpoint":"https://localhost:5001/connect/endsession","check_session_iframe":"https://localhost:5001/connect/checksession","revocation_endpoint":"https://localhost:5001/connect/revocation","introspection_endpoint":"https://localhost:5001/connect/introspect","device_authorization_endpoint":"https://localhost:5001/connect/deviceauthorization","frontchannel_logout_supported":true,"frontchannel_logout_session_supported":true,"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"scopes_supported":["openid","api1","offline_access"],"claims_supported":["sub","UserClaim1","UserClaim2"],"grant_types_supported":["authorization_code","client_credentials","refresh_token","implicit","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","private_key_jwt"],"id_token_signing_alg_values_supported":["RS256"],"subject_types_supported":["public"],"code_challenge_methods_supported":["plain","S256"],"request_parameter_supported":true},"Exception":null,"IsError":false,"ErrorType":0,"HttpStatusCode":200,"HttpErrorReason":"OK","Error":null}
============================================================================================
Token Response:
============================================================================================
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjU3RURBRUJFQzY4RjNDQUFDRTg2OUUzRkEyMjZDMEZGIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE1OTM0NjE2OTAsImV4cCI6MTU5MzQ2NTI5MCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMSIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjUwMDEvcmVzb3VyY2VzIiwiY2xpZW50X2lkIjoiY2xpZW50IiwianRpIjoiNEY5QzczMDZBRjdFMURDNjI3QkRBQTdCRjg4MjlDNTMiLCJpYXQiOjE1OTM0NjE2OTAsInNjb3BlIjpbImFwaTEiXX0.FxvjG89zv1a83MtyjJvzCA26g_VLO6HTElJuOSi1FOp_My1RGHB-mbg53E6jZF9Xq_pkAOak5SC73tMC0b3hcEGx9O1qsd9c_Q9ish2ffmCZZ34svkpsfZp3wjbS-xNyxq7mjSOg0JGpf3ML_eUz3TUcOa5Aba_evzmRDaVgAvEtsdM8D7lK_udnQmw0cDimc8vYaGSLIXJDfOhM9pb-8I67deElCxaIEG93CwRZV5bwQQQC3dLwihb51wndv962Kw0dPkIXrt1n7jwEQ4KAhBqVcP9DAgPTqem1Kix8Uq_P4wBTm_cMY7U7bCa-j6mvRZ8t7TxWARpylzlL-ojy7g",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "api1"
}
============================================================================================
Calling api endpoint https://localhost:6001/MyEndpoint/Get
============================================================================================
============================================================================================
Request
============================================================================================
Request:
Method: GET, RequestUri: 'https://localhost:6001/MyEndpoint/Get', Version: 1.1, Content: <null>, Headers:
{
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjU3RURBRUJFQzY4RjNDQUFDRTg2OUUzRkEyMjZDMEZGIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE1OTM0NjE2OTAsImV4cCI6MTU5MzQ2NTI5MCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMSIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjUwMDEvcmVzb3VyY2VzIiwiY2xpZW50X2lkIjoiY2xpZW50IiwianRpIjoiNEY5QzczMDZBRjdFMURDNjI3QkRBQTdCRjg4MjlDNTMiLCJpYXQiOjE1OTM0NjE2OTAsInNjb3BlIjpbImFwaTEiXX0.FxvjG89zv1a83MtyjJvzCA26g_VLO6HTElJuOSi1FOp_My1RGHB-mbg53E6jZF9Xq_pkAOak5SC73tMC0b3hcEGx9O1qsd9c_Q9ish2ffmCZZ34svkpsfZp3wjbS-xNyxq7mjSOg0JGpf3ML_eUz3TUcOa5Aba_evzmRDaVgAvEtsdM8D7lK_udnQmw0cDimc8vYaGSLIXJDfOhM9pb-8I67deElCxaIEG93CwRZV5bwQQQC3dLwihb51wndv962Kw0dPkIXrt1n7jwEQ4KAhBqVcP9DAgPTqem1Kix8Uq_P4wBTm_cMY7U7bCa-j6mvRZ8t7TxWARpylzlL-ojy7g
}
Response:
StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
Date: Mon, 29 Jun 2020 20:14:50 GMT
Server: Kestrel
WWW-Authenticate: Bearer error="invalid_token", error_description="The audience 'https://localhost:5001/resources' is invalid"
Content-Length: 0
}
============================================================================================
Response
============================================================================================
Unauthorized
IdentityServer output:
[14:05:23 Information]
Starting host...
[14:05:24 Information] IdentityServer4.Startup
Starting IdentityServer4 version 4.0.0+1acafade44176bf817412aa4309d5dff6587a741
[14:05:24 Information] IdentityServer4.Startup
You are using the in-memory version of the persisted grant store. This will store consent decisions, authorization codes, refresh and reference tokens in memory only. If you are using any of those features in production, you want to switch to a different store implementation.
[14:05:24 Information] IdentityServer4.Startup
Using the default authentication scheme idsrv for IdentityServer
[14:05:24 Debug] IdentityServer4.Startup
Using idsrv as default ASP.NET Core scheme for authentication
[14:05:24 Debug] IdentityServer4.Startup
Using idsrv as default ASP.NET Core scheme for sign-in
[14:05:24 Debug] IdentityServer4.Startup
Using idsrv as default ASP.NET Core scheme for sign-out
[14:05:24 Debug] IdentityServer4.Startup
Using idsrv as default ASP.NET Core scheme for challenge
[14:05:24 Debug] IdentityServer4.Startup
Using idsrv as default ASP.NET Core scheme for forbid
[14:05:24 Information] Microsoft.Hosting.Lifetime
Now listening on: https://localhost:5001
[14:05:24 Information] Microsoft.Hosting.Lifetime
Application started. Press Ctrl+C to shut down.
[14:05:24 Information] Microsoft.Hosting.Lifetime
Hosting environment: Development
[14:05:24 Information] Microsoft.Hosting.Lifetime
Content root path: C:\Source\Repos\IdentityServer\code\IdentityServer
[14:05:26 Debug] IdentityServer4.Startup
Login Url: /Account/Login
[14:05:26 Debug] IdentityServer4.Startup
Login Return Url Parameter: ReturnUrl
[14:05:26 Debug] IdentityServer4.Startup
Logout Url: /Account/Logout
[14:05:26 Debug] IdentityServer4.Startup
ConsentUrl Url: /consent
[14:05:26 Debug] IdentityServer4.Startup
Consent Return Url Parameter: returnUrl
[14:05:26 Debug] IdentityServer4.Startup
Error Url: /home/error
[14:05:26 Debug] IdentityServer4.Startup
Error Id Parameter: errorId
[14:05:33 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /.well-known/openid-configuration matched to endpoint type Discovery
[14:05:33 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Discovery, successfully created handler: IdentityServer4.Endpoints.DiscoveryEndpoint
[14:05:33 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryEndpoint for /.well-known/openid-configuration
[14:05:33 Debug] IdentityServer4.Endpoints.DiscoveryEndpoint
Start discovery request
[14:05:33 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /.well-known/openid-configuration/jwks matched to endpoint type Discovery
[14:05:33 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Discovery, successfully created handler: IdentityServer4.Endpoints.DiscoveryKeyEndpoint
[14:05:33 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryKeyEndpoint for /.well-known/openid-configuration/jwks
[14:05:33 Debug] IdentityServer4.Endpoints.DiscoveryKeyEndpoint
Start key discovery request
[14:05:33 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /connect/token matched to endpoint type Token
[14:05:33 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Token, successfully created handler: IdentityServer4.Endpoints.TokenEndpoint
[14:05:33 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.TokenEndpoint for /connect/token
[14:05:33 Debug] IdentityServer4.Endpoints.TokenEndpoint
Start token request.
[14:05:33 Debug] IdentityServer4.Validation.ClientSecretValidator
Start client validation
[14:05:33 Debug] IdentityServer4.Validation.BasicAuthenticationSecretParser
Start parsing Basic Authentication secret
[14:05:33 Debug] IdentityServer4.Validation.PostBodySecretParser
Start parsing for secret in post body
[14:05:33 Debug] IdentityServer4.Validation.ISecretsListParser
Parser found secret: PostBodySecretParser
[14:05:33 Debug] IdentityServer4.Validation.ISecretsListParser
Secret id found: client
[14:05:33 Debug] IdentityServer4.Stores.ValidatingClientStore
client configuration validation for client client succeeded.
[14:05:33 Debug] IdentityServer4.Validation.ISecretsListValidator
Secret validator success: HashedSharedSecretValidator
[14:05:33 Debug] IdentityServer4.Validation.ClientSecretValidator
Client validation success
[14:05:33 Debug] IdentityServer4.Validation.TokenRequestValidator
Start token request validation
[14:05:33 Debug] IdentityServer4.Validation.TokenRequestValidator
Start client credentials token request validation
[14:05:33 Debug] IdentityServer4.Validation.TokenRequestValidator
client credentials token request validation success
[14:05:33 Information] IdentityServer4.Validation.TokenRequestValidator
Token request validation success, {"ClientId": "client", "ClientName": null, "GrantType": "client_credentials", "Scopes": "api1", "AuthorizationCode": null, "RefreshToken": null, "UserName": null, "AuthenticationContextReferenceClasses": null, "Tenant": null, "IdP": null, "Raw": {"grant_type": "client_credentials", "scope": "api1", "client_id": "client", "client_secret": "***REDACTED***"}, "$type": "TokenRequestValidationLog"}
[14:05:33 Debug] IdentityServer4.Services.DefaultClaimsService
Getting claims for access token for client: client
[14:05:33 Debug] IdentityServer4.Endpoints.TokenEndpoint
Token request success.
[14:05:34 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /.well-known/openid-configuration matched to endpoint type Discovery
[14:05:34 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Discovery, successfully created handler: IdentityServer4.Endpoints.DiscoveryEndpoint
[14:05:34 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryEndpoint for /.well-known/openid-configuration
[14:05:34 Debug] IdentityServer4.Endpoints.DiscoveryEndpoint
Start discovery request
[14:05:34 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /.well-known/openid-configuration/jwks matched to endpoint type Discovery
[14:05:34 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Discovery, successfully created handler: IdentityServer4.Endpoints.DiscoveryKeyEndpoint
[14:05:34 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryKeyEndpoint for /.well-known/openid-configuration/jwks
[14:05:34 Debug] IdentityServer4.Endpoints.DiscoveryKeyEndpoint
Start key discovery request
Ok, I found the way to fix this error but it opens up other issues.
First, looked at the generated JWT token. It shows Audience as "https://localhost:5001/resources". So I scanned the code for where the audience validation is done, which is in the API that authenticates the caller via IS4's Startup.cs file. Changed the audience value to the expected value, and it worked:
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
// Use our IS4 implementation as the authentication source.
options.Authority = "https://localhost:5001";
options.RequireHttpsMetadata = false;
//options.Audience = "api1";
options.Audience = "https://localhost:5001/resources";
});
This does not, however, tell me how that value was generated or where to override it, but does fix the issue.
With What your current settings on API, you need is an aud as api1 in the access_token. To fix just add the scopes to the API resource. Verify the generated token on https://jwt.ms/
public static IEnumerable<ApiResource> Apis =>
new List<ApiResource>
{
new ApiResource("api1", "My API")
{
Scopes = { "api1"}
}
};
and revert back your API settings to have options.Audience = "api1";.
Read more about API resource here
I had the same issue and found how to fix it. In Identity server 4 configuration you must add scope with name which you want to use.
Here is the example how to declare an api.
public static IEnumerable<ApiResource> Apis =>
new ApiResource[]
{
new ApiResource("adminApi", "Admin Panel Service")
{
Scopes = {
"adminApi"
}
}
};
And add this to your configuration.
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiResources(Config.Apis)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddAspNetIdentity<ApplicationUser>()
.AddJwtBearerClientAuthentication();
As far as I understood the documentation, the value "'https://localhost:5001/resources" is generated when you use options.EmitStaticAudienceClaim = true;.
If you need an aud claim, you can enable the EmitStaticAudience
setting on the options. This will emit an aud claim in the
issuer_name/resources format. If you need more control of the aud
claim, use API resources.
I found it here:
Authorization based on Scopes
To have "api1" in your "aud" section of the token you need to follow nahidf's and Vesko I's suggestions:
In you Config.cs File add an ApiResource:
public static IEnumerable<ApiResource> ApiResources =>
new ApiResource[]
{
new ApiResource("api1", "Test API")
{
Scopes = { "api1.read", "api1.write" }
}
};
and in you Startup.cs add that resource to you ApiResources registration:
.AddInMemoryApiResources(Config.ApiResources)
After doing so, Identity Server will create tokens that contain this:
"aud": [
"api1",
"https://localhost:44300/resources"
],

Identity Server 4 Silent Renew ErrorResponse: login_required

I have cloned the repo from the redux-oidc-example and it works for the most part but after a few hours it gives the following error:
Action payload: ErrorResponse: login_required
at new e (oidc-client.min.js:1)
at t [as _processSigninParams] (oidc-client.min.js:1)
at t [as validateSigninResponse] (oidc-client.min.js:1)
at oidc-client.min.js:1
UserManager.js looks like this:
const userManagerConfig = {
client_id: 'js.dev',
client_secret: 'secret',
redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/callback`,
response_type: 'id_token token',
scope: 'openid email profile role offline_access',
authority: 'http://localhost:8080',
silent_redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/silent_renew.html`,
automaticSilentRenew: true,
filterProtocolClaims: true,
loadUserInfo: true
};
and my identity server config:
{
"Enabled": true,
"ClientId": "js.dev",
"ClientName": "Javascript Client",
"ClientSecrets": [ { "Value": "K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=" } ],
"AllowedGrantTypes": [ "implicit", "authorization_code" ],
"AllowedScopes": [ "openid", "email", "profile", "role", "offline_access" ],
"AllowOfflineAccess": true,
"AllowAccessTokensViaBrowser":true,
"RedirectUris": [
"http://localhost:8081/callback",
"http://localhost:8081/silent_renew.html"
],
"PostLogoutRedirectUris": [
"http://localhost:8081"
],
"AccessTokenLifetime": 900,
"RequireConsent": false
}
I noticed that prior to error last valid response had one cookie response(idsrv.session) with empty value with the expiry date set to the previous year:
I believe this to be the root cause of the issue, I searched it on related Github repo and tried to add the Cookie.SameSite to none but it didn't help:
services.AddAuthentication()
.AddSaml(Configuration,externalProviders.UseSaml)
.AddCookie(options => {
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.Cookie.SameSite = SameSiteMode.None;
});
Any idea!
This is likely due to your IDP session expiring - if you call the authorize endpoint with prompt=none but it's unable to satisfy that request because no valid session exists (i.e. authentication cookie does not exist or has expired) then it will return error=login_required.
If this occurs then the correct course of action is to do an interactive (i.e. prompt=login) sign in request in the top level browser window.
After searching the Identity Server 4 repo, I made the following changes to my code:
services.AddIdentityServer(options=>
{
options.Authentication.CookieLifetime = TimeSpan.FromDays(30);
options.Authentication.CookieSlidingExpiration = true;
})
.AddProfileService<ProfileService>()
.AddSigningCertificate(Configuration)
.AddInMemoryClients(Configuration.GetSection("IdentityServer:Clients"))
.AddInMemoryIdentityResources(Resources.GetIdentityResources());
It started working afterward, but you would have to login again after you close the browser or reopen a new tab I guess it's because of the sessionStorage.
When the session expires the signin-callback is being called by STS having a query parameter called 'error' with the value 'login_required'.
In the signin-callback, before completing sign-in, you can check for this query parameter and if it's found you can sign-out also from your web client.
I had the same issue and tried the proposed above, but for me, it actually was SameSiteMode not set correctly on IdentityServer Cookies. It caused Callback error: ErrorResponse: login_required right after login and after N attempts user was logged out.
This helped me https://github.com/IdentityServer/IdentityServer4/blob/main/src/IdentityServer4/host/Extensions/SameSiteHandlingExtensions.cs
What they do is based on this article https://devblogs.microsoft.com/dotnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/
Hope this is useful.
Update.
I had another issue related to this when the user was logged out after re-opening a browser (especially on Android Chrome). login_required error was shown. I noticed that session cookie Expires/Max-Age was set to Session and not some future date. Probably because of that check session iframe (with src={identity server url}/connect/checksession) failed as Identity Server thought there was no session as cookie expired.
I tried setting cookie lifetime via options, but it didn't work as expected for some reason. Lifetime was always 14 days:
services.AddIdentityServer(options=>
options.Authentication.CookieLifetime = TimeSpan.FromDays(30);
options.Authentication.CookieSlidingExpiration = true;
})
Then I tried this and it worked for me:
services.ConfigureApplicationCookie(options => {
options.ExpireTimeSpan = sessionCookieLifetime;
options.SlidingExpiration = true;
})

IdentityServer4 client redirectURI issue

I have the following client configured on the IdentityServer4 host side:
new Client
{
ClientId = "myClient",
....
RedirectUris = {"https://localhost:44001/home/claims",
"https://localhost:44001/"}
....
}
I am currently trying to get it to return in such a way that it works regardless of which controller in the client app it originates from (since redirectURI is configured at startup time on the client app & has to match the server's client config).
In the client app startup, the relevant code:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
...
ClientId = "myClient",
RedirectUri = "https://localhost:44001/",
...
}
When I set the redirectUri to the second option above, "https://localhost:44001" on the client startup, I get redirected to the client error controller with the following error:
"Sorry, there was an error : unauthorized_client Invalid
redirect_uri".
In addition, the server outputs the following error to console:
Exception thrown: 'System.FormatException' in
System.Private.CoreLib.dll
IdentityServer4.Validation.AuthorizeRequestValidator:Error: Invalid
redirect_uri: https://localhost:44001/ { "ClientId": "myClient",
... "AllowedRedirectUris": [
"https://localhost:44001/home/claims",
"https://localhost:44001/" ], "SubjectId": "anonymous", "RequestedScopes": "", "Raw": {
"client_id": "myClient",
"redirect_uri": "https://localhost:44001/",
"response_mode": "form_post",
"response_type": "code id_token",
... } }
It's almost like you cannot have multiple redirect URIs for a client, but that can't be right.

How to request additional claims for access token in identity server 4 / auth code flow?

How do you request additional claims for the access token jwt in identity server 4 / auth code flow? My custom profile service always shows RequestedClaimTypes of 0 during my auth code flow signin so the resulting access token jwt has my subject claim but no firstname, lastname, or email claim.
Here are my requested scopes from the client:
"TestApi openid profile email"
Here is my client definition on identity server:
new Client {
ClientId = "authorizationCodeClient2",
ClientName = "Authorization Code Test",
ClientSecrets = {
new Secret("secret".Sha256())
},
Enabled = true,
AllowedGrantTypes = GrantTypes.Code,
RequireConsent = true,
AllowRememberConsent = false,
RedirectUris =
new List<string> {
"http://localhost:5436/account/oAuth2"
},
AllowedScopes = { "TestApi", "openid", "profile", "email" },
AccessTokenType = AccessTokenType.Jwt
}
Using https://github.com/bayardw/IdentityServer4.Authorization.Code for the test client.
I discovered that identity server will let you optionally stamp the id token with the user profile claims (instead of having to call the userinfo endpoint). You basically set a Boolean property for that particular client:
AlwaysIncludeUserClaimsInIdToken = true;
Note, you will want to request the following scopes on your auth request : (openid profile email)

Resources