I'm trying to create react spa with authentication. I'm using identityserver4 for OIDC provider. Whenever I login, I am redirected to login page and after providing credentials, I'm redirected to SPA, but UserManger does not have access_token.
I've tried to play with Client on server side, but none of that worked.
For Frontend I'm using this example https://github.com/Franpastoragusti/oidc-react-app , but converted to typescript
And backend is slightly modified Identityserver quickstart.
This is my Client in Config.cs
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "primrose.web",
ClientName = "Primrose Web Application",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
AccessTokenType = AccessTokenType.Jwt,
RequireConsent = false,
AccessTokenLifetime = 120,
AlwaysIncludeUserClaimsInIdToken = true,
RedirectUris = {
"http://localhost:3000/callback",
"http://localhost:3000/silentRenew",
},
PostLogoutRedirectUris =
{
"http://localhost:5000/account/login"
},
AllowedCorsOrigins = { "http://localhost:3000" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"primrose.web"
},
}
};
}
This is my oidc configuration in client app:
export const IDENTITY_CONFIG: any = {
authority: "http://localhost:5000", //(string): The URL of the OIDC provider.
client_id: "primrose.web", //(string): Your client application's identifier as registered with the OIDC provider.
redirect_uri: "http://localhost:3000/callback", //The URI of your client application to receive a response from the OIDC provider.
login: "http://localhost:5000/account/login",
automaticSilentRenew: false, //(boolean, default: false): Flag to indicate if there should be an automatic attempt to renew the access token prior to its expiration.
loadUserInfo: true, //(boolean, default: true): Flag to control if additional identity data is loaded from the user info endpoint in order to populate the user's profile.
silent_redirect_uri: "http://localhost:3000/silentRenew", //(string): The URL for the page containing the code handling the silent renew.
post_logout_redirect_uri: "http://localhost:5000/account/login", // (string): The OIDC post-logout redirect URI.
audience: "primrose.web", //is there a way to specific the audience when making the jwt
responseType: "id_token token", //(string, default: 'id_token'): The type of response desired from the OIDC provider.
grantType: "password",
scope: "openid profile primrose.web", //(string, default: 'openid'): The scope being requested from the OIDC provider.
webAuthResponseType: "id_token token"
};
export const METADATA_OIDC: any = {
issuer: "http://localhost:5000",
jwks_uri: "http://localhost:5000/.well-known/openid-configuration/jwks",
authorization_endpoint: "http://localhost:5000/connect/authorize",
token_endpoint: "http://localhost:5000/connect/token",
userinfo_endpoint: "http://localhost:5000/connect/userinfo",
end_session_endpoint: "http://localhost:5000/connect/endsession",
check_session_iframe: "http://localhost:5000/connect/checksession",
revocation_endpoint: "http://localhost:5000/connect/revocation",
introspection_endpoint: "http://localhost:5000/connect/introspect"
};
this is what usermanager returns when addUserLoaded event is fired
access_token: undefined
expires_at: undefined
id_token: "eyJhbGciOiJSU...(I deleted rest of the token)"
profile:
amr: ["pwd"]
auth_time: 1564597858
email: "pipa#papa.pl"
idp: "local"
name: "pipa#papa.pl"
preferred_username: "pipa#papa.pl"
sid: "2044d39c30e316722516f6d376e24838"
sub: "c0ca848c-8d2d-48e6-829f-e1d29838ecef"
__proto__: Object
refresh_token: undefined
scope: "openid profile primrose.web"
session_state: "kmFYyCDxsql8D4-WD7J07OwW4zhMDdqLjDkHriNuwqg.852977a2a19ba24a7bb5a12d389bebfd"
state: undefined
token_type: undefined
expired: (...)
expires_in: (...)
scopes: (...)
__proto__: Object
I want to retrieve this access token from OIDC provider in my client app, but it comes undefined
Related
We have just upgraded our web app to .NET 5 and IdentityServer4 to V4, also we switched from hybrid flow to code + PKCE. The client is set to an access token type of reference, also the client uses bearer tokens for an internal API as well as cookies for the main website.
When we deploy to our internal development server (IIS 8.5) or to Azure App Services randomly when we ask for the access token (reference) we are returned an access token (JWT) instead. We did use the httpContext.GetTokenAsync() method but then replaced it with the identitymodel.aspnetcore GetUserAccessTokenAsync() method but it still returns a JWT Token.
I have validated the JWT tokens contents and they are the relevant user and their claims. I have also checked the persisted grant table and the reference token entered in there specifies it as a JWT instead of Reference.
The only way to rectify the situation is
Stop the website and identityserver
Clear the cookies in the browser
Delete all entries in the persisted grants table
Recycle the app pools
Start the identity server and then perform a login
Start the website which the login is for and suddenly we get a reference access token again
Identity Server Client Config
AllowedGrantTypes =
{
GrantType.AuthorizationCode,
"exchange_reference_token"
},
AccessTokenType = AccessTokenType.Reference,
AccessTokenLifetime = 86400,
RequireConsent = false,
AllowAccessTokensViaBrowser = true,
ClientSecrets =
{
new Secret("*******)
},
RedirectUris = { $"{client}/signin-oidc" },
PostLogoutRedirectUris = { $"{client}/signout-callback-oidc" },
FrontChannelLogoutUri = $"{client}/signout-oidc",
AllowedCorsOrigins = { client },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"roles",
"API1",
"API2",
"API3",
"Signalr"
},
UpdateAccessTokenClaimsOnRefresh = true,
AllowOfflineAccess = true
I've found out what is causing the issue. We are using signalr in the website and we are having to use the exchange_reference_token, however in the exchange code it forces the access token to JWT, this then gets inserted in the persisted grant table causing any future request to return JWT instead of of reference access token
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
var referenceToken = context.Request.Raw.Get("token");
if (referenceToken == null)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Missing Reference Token");
}
var result = await _validator.ValidateAccessTokenAsync(referenceToken);
if (result == null)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Invalid Reference Token");
}
// Generate the JWT as if it was for the reference token's client
context.Request.Client.AccessTokenType = AccessTokenType.Jwt;
var sub = result.Claims.FirstOrDefault(c => c.Type == "sub").Value;
context.Result = new GrantValidationResult(sub, GrantType, result.Claims);
}
I have a application which is like Microsoft default templates of Asp.netCoreWebApplication->ASP.netCoreWithReact.js.
In this react client will be wrapped inside a.netcore project. All of the UI pages will be served from React. .Net core backend will be used only for APIs.
Now I have implemented IdentityServer4 and able to generate token at this end point,
http://localhost:60739/token
From react client, on login button click I could make API call to http://localhost:60739/token and could generate token using granttype password flow. but i could not validate authroize my api end points with that token
below is my client definition with in identity server solution,
new Client
{
ClientName = "Resource Owner Flow",
ClientId = "resource_owner_flow",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("resource_owner_flow_secret".Sha256())
},
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.OfflineAccess
},
AllowOfflineAccess = true,
RefreshTokenUsage = TokenUsage.ReUse,
//AccessTokenLifetime = 60,
RefreshTokenExpiration = TokenExpiration.Absolute,
AbsoluteRefreshTokenLifetime = 300
}
below is piece of code i am using to refer identity server,
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "http://localhost:60739";
options.TokenValidationParameters.ValidateAudience = false;
options.TokenValidationParameters.ValidateIssuer = false;
options.TokenValidationParameters.ValidateIssuerSigningKey = false;
options.TokenValidationParameters.ValidateLifetime = false;
options.TokenValidationParameters.ValidateTokenReplay = false;
options.IncludeErrorDetails = true;
});
I could not successfully access protected api resources? Any idea ?
If i Call http://localhost:57102/api/Home/GetSomeProtectedData using token in header i get 500 internal server error. If i remove authorize attribute on protected resource it's working fine.
below is postman screenshot,
for token creation below is the log,
IdentityServer4.Validation.TokenRequestValidator: Information: Token request validation success,
{
"ClientId": "resource_owner",
"ClientName": "Resource Owner",
"GrantType": "password",
"Scopes": "openid",
"AuthorizationCode": "********",
"RefreshToken": "********",
"UserName": "superadmin#gmail.com",
"Raw": {
"username": "superadmin#gmail.com",
"password": "***REDACTED***",
"grant_type": "password",
"scope": "openid",
"response_type": "token"
}
}
but when I request protected API resource with token in header i get 500 internal server error and logs got created.
If you are getting 500 error, the problem is not the authorization flow. Else it would be 401.
You need to check out what is the real error. It can be a parsing error. You simpyly cannot finish the process because of an server side error. Please share more info, may be logs so I update the answer.
I'm using code flow for a vuejs client with Identityserver4.
i added RequirePkce and i can get the access token and id token from oidc-client.
but access token aud claim is pointing back to Identityserver4 base address not my api resource.
can something be wrong ?
Client:
new Client
{
ClientId = "js.admin",
ClientName = "admin dashboard vuejs client.",
RequirePkce = true,
RequireClientSecret = false,
RequireConsent = false,
AllowedGrantTypes = GrantTypes.Code,
AllowAccessTokensViaBrowser = true,
RedirectUris = new List<string>
{
"http://localhost:8080",
"http://localhost:8080/logincb.html",
"http://localhost:8080/silent-renew.html"
},
PostLogoutRedirectUris = new List<string>
{
"http://localhost:8080/",
"http://localhost:8080"
},
AllowedCorsOrigins = new List<string>
{
"http://localhost:8080"
},
AllowedScopes = new List<string>
{
"openid",
"role",
"profile",
"api1.rw",
"email",
"phone"
}
}
oidc client setting:
const clientSettings = {
userStore: new WebStorageStateStore({ store: window.localStorage }),
authority: STS_DOMAIN,
client_id: "js.admin",
redirect_uri: "http://localhost:8080/logincb.html",
automaticSilentRenew: true,
silent_redirect_uri: "http://localhost:8080/silent-renew.html",
response_type: "code",
scope: "openid profile api1.rw role email phone",
post_logout_redirect_uri: "http://localhost:8080/",
filterProtocolClaims: true
};
Access token decoded:
"iss": "http://localhost:5001",
"aud": "http://localhost:5001/resources",
as you can see the both issuer and audience claims are the same with is wrong.
but even scopes are correct.
I really appreciate any help.
Bearer was not authenticated. Failure message: IDX10214: Audience validation failed. Audiences: 'http://localhost:5001/resources'. Did not match: validationParameters.ValidAudience: 'api1' or validationParameters.ValidAudiences: 'null'.
its is the last error i got.
The http://localhost:5001/resources is a generic resource that is added when you have not defined or associated any ApiResources with the requested ApiScope.
From the documentation here, it says:
When using the scope-only model, no aud (audience) claim will be added
to the token since this concept does not apply. If you need an aud
claim, you can enable the EmitStaticAudienceClaim 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.
To get api1.rw as your audience, you need to add a ApiResource to your IdentityServer configuration. You can name the ApiResource and ApiScope api1.rw
To complement this answer, I write a blog post that goes into more detail about this topic:
IdentityServer – IdentityResource vs. ApiResource vs. ApiScope
I have setup Identity Server 4 for my project using Entity Framework. I already configured the service to use a persisted grant Store and a Signed Certificate.
services.AddIdentityServer()
.AddSigningCredential(Config.GetSigningCertificate())
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
.AddProfileService<ProfileService>()
.AddConfigurationStore(builder =>
builder.UseSqlServer(connectionString, options =>
options.MigrationsAssembly(migrationsAssembly)))
.AddOperationalStore(builder =>
builder.UseSqlServer(connectionString, options =>
options.MigrationsAssembly(migrationsAssembly)));
Here is the configuration of the service.
The problem is when I run my server behind a load balancer with for exemple 2 identic instances handling all the request, the server where the user did not logged in fail to decode the JWT token, leading to 401 unauthorized errors.
I'm assuming the sigining method of the tokens or their encription is the problem but I cannot find a way to solve this.
Here is the rest of my configuration.
The Configure:
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = url,
// Authority = "http://localhost:5000",
AllowedScopes = { "WebAPI" },
RequireHttpsMetadata = false,
AutomaticAuthenticate = true,
AutomaticChallenge = true,
});
the Client:
new Client
{
ClientId = "Angular2SPA",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, // Resource Owner Password Credential grant.
AllowAccessTokensViaBrowser = true,
RequireClientSecret = false, // This client does not need a secret to request tokens from the token endpoint.
AccessTokenLifetime = 7200, // Lifetime of access token in seconds.
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId, // For UserInfo endpoint.
IdentityServerConstants.StandardScopes.Profile,
"roles",
"WebAPI"
},
AllowOfflineAccess = true, // For refresh token.
AccessTokenType = AccessTokenType.Jwt
}
I also implemented my own IResourceOwnerPasswordValidator and IProfileService.
Any idea why is this happening?
I had a similar issue, load balancing Identity Server 4 and was able to share the keys using .AddDataProtection() in ConfigureServices of Startup.cs .
public void ConfigureServices(IServiceCollection services)
{
// Other service configurations
services.AddDataProtection();
// Additional service configurations
}
As a side note, if you go this route, consider encrypting those keys (in whichever medium you decide to use) using an extension like
.ProtectKeysWith* (there are several options)
. See https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/introduction?view=aspnetcore-2.1 for further information
HTH
Short: My client retrieves an access token from IdentityServer sample server, and then passes it to my WebApi. In my controller, this.HttpContext.User.GetUserId() returns null (User has other claims though). I suspect access token does not have nameidentity claim in it. How do I make IdentityServer include it?
What I've tried so far:
switched from hybrid to implicit flow (random attempt)
in IdSvrHost scope definition I've added
Claims = { new ScopeClaim(ClaimTypes.NameIdentifier, alwaysInclude: true) }
in IdSvrHost client definition I've added
Claims = { new Claim(ClaimTypes.NameIdentifier, "42") }
(also a random attempt)
I've also tried other scopes in scope definition, and neither of them appeared. It seems, that nameidentity is usually included in identity token, but for most public APIs I am aware of, you don't provide identity token to the server.
More details:
IdSrvHost and Api are on different hosts.
Controller has [Authorize]. In fact, I can see other claims coming.
Api is configured with
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.UseIdentityServerAuthentication(options => {
options.Authority = "http://localhost:22530/";
// TODO: how to use multiple optional scopes?
options.ScopeName = "borrow.slave";
options.AdditionalScopes = new[] { "borrow.receiver", "borrow.manager" };
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
});
Scope:
public static Scope Slave { get; } = new Scope {
Name = "borrow.slave",
DisplayName = "List assigned tasks",
Type = ScopeType.Resource,
Claims = {
new ScopeClaim(ClaimTypes.NameIdentifier, alwaysInclude: true),
},
};
And client:
new Client {
ClientId = "borrow_node",
ClientName = "Borrow Node",
Flow = Flows.Implicit,
RedirectUris = new List<string>
{
"borrow_node:redirect-target",
},
Claims = { new Claim(ClaimTypes.NameIdentifier, "42") },
AllowedScopes = {
StandardScopes.OpenId.Name,
//StandardScopes.OfflineAccess.Name,
BorrowScopes.Slave.Name,
},
}
Auth URI:
request.CreateAuthorizeUrl(
clientId: "borrow_node",
responseType: "token",
scope: "borrow.slave",
redirectUri: "borrow_node:redirect-target",
state: state,
nonce: nonce);
and I also tried
request.CreateAuthorizeUrl(
clientId: "borrow_node",
responseType: "id_token token",
scope: "openid borrow.slave",
redirectUri: "borrow_node:redirect-target",
state: state,
nonce: nonce);
Hooray, I found an answer, when I stumbled upon this page: https://github.com/IdentityServer/IdentityServer3.Samples/issues/173
Apparently, user identity is passed in "sub" claim in the access token. Because I blindly copied API sample, its configuration included
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
which essentially prevented my API from mapping "sub" claim to nameidentifier. After removing this line, HttpContext.User.GetUserId() of authenticated controller returns user ID correctly.