I know there are a lot of question about this but nothing I am finding is resolving my issue.
Setup:
Server
services.AddControllersWithViews();
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddTestUsers(TestUsers.Users);
builder.AddDeveloperSigningCredential();
Config.Clients
// resource owner password grant client
new Client
{
ClientId = "ro.client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "api1" }
}
TestUsers.Users
new TestUser
{
SubjectId = "818727",
Username = "alice",
Password = "alice",
Claims =
{
new Claim(JwtClaimTypes.Name, "Alice Smith"),
new Claim(JwtClaimTypes.GivenName, "Alice"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "AliceSmith#email.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.WebSite, "http://alice.com"),
new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address), IdentityServerConstants.ClaimValueTypes.Json)
}
}
Call via postman:
Response in postman:
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkE1RkQ2QzlFN0I0QTYzRjJDOERBM0IyRDkxODA1MTAxIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2MjE1NDk1OTYsImV4cCI6MTYyMTU1MzE5NiwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMSIsImNsaWVudF9pZCI6InJvLmNsaWVudCIsInN1YiI6IjgxODcyNyIsImF1dGhfdGltZSI6MTYyMTU0OTU5NiwiaWRwIjoibG9jYWwiLCJqdGkiOiJFNDYxMjg1NkRDMzhBNkYxRTE3OTRGRkE2OURGMjY2MiIsImlhdCI6MTYyMTU0OTU5Niwic2NvcGUiOlsiYXBpMSJdLCJhbXIiOlsicHdkIl19.mThB_nrdwF28hvSDGllfZuZLIAaHirUzf0XJpkkVAjtJeWh6t73uf05Zbtuxc0pvh81MSvAs38firgXew00XqBHKJtEE8aLQpitnzu5SUfAsAlLCz281T9V7yH-YblBTm5z9ZR-5rJBoqFwbPQ8j_GhZeCxskFKbpjIcdfXcndSBFywu2yM5NEow3YNdtxdFHqChDt1WNT9Mk0GV17iN0Rg6ZlMbgZcF0zEinJDfccvMWyWvbWvqvtl4E6Yq53kEKiD8Y5p3HU2Bac8J54sXVuhFKWammLkZwnF2Qw0h_cFkoocxmA6lyeXlLb2vWqmcdZg_fi_M2SHKuKm6GHT-6w",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "api1"
}
However when I paste the access_token into jwt.io the token is decoded fine with everything that I expect but is comes up as an invalid signature. This is affecting the validating the token in other places.
Can anyone help me?
When you use this in the server:
builder.AddDeveloperSigningCredential();
IdentityServer will regenerate new signing keys on each deployment if the existing keyfile is not found. So in production you should not use AddDeveloperSigningCredential instead you should use the AddSigningCredential method to add a more permanent key.
see this page
Related
I'm using resource owner password grand type. I implemented 2 interfaces: IResourceOwnerPasswordValidator and IProfileService. When I request a token, I get to the ResourceOwnerPasswordValidator, after successful validation I expect ProfileService provides me with requested climes. But it never does.
The thing is the RequestedClaimTypes from ProfileDataRequestContext in the ProfileService is always empty, as a result I can't filter out claims which weren't asked.
I expect it contains all claims, which are related to the requested scopes.
For example if I request such scopes as email or "profile" then I expect claims like "email", "first_name", "preferred_username" and others to be in the RequestedClaimTypes list.
There is simple clients and identity resource config:
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email()
};
...........................................................................
new Client
{
ClientId = "client",
ClientName = "SomeClient",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowOfflineAccess = true,
UpdateAccessTokenClaimsOnRefresh = true,
RefreshTokenUsage = TokenUsage.ReUse,
SlidingRefreshTokenLifetime = 1200,
RefreshTokenExpiration = TokenExpiration.Sliding,
AccessTokenLifetime = 900,
IdentityTokenLifetime = 120,
AllowedGrantTypes = { GrantType.ResourceOwnerPassword },
AccessTokenType = AccessTokenType.Jwt,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email
}
},
Interesting fact is that RequextedResource from ProfileDataRequestContext has claims data, which I can use to figure out what claims user has requested. For example:
var claimsNames = new List<string>();
claimsNames.AddRange(context.RequestedResources.IdentityResources.SelectMany(r => r.UserClaims));
claimsNames.AddRange(context.RequestedResources.ApiResources.SelectMany(r => r.UserClaims));
context.RequestedClaimTypes = claimsNames;
But it seems to be hacky way. Does someone know why RequestedClaimTypes isn't populated automatically?
P.S Client is the postman with the following parameters
We are using IdentityServer4 to protect our APIs with EntityFrameworkCore to store configuration and operational data.
Here is our client data:
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" },
AllowOfflineAccess=true
},
new Client
{
ClientId = "client2",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "sup_api" },
AllowOfflineAccess=true
}
};
}
We posted request to connect/token endpoint,with following data in "x-www-form-urlencoded" format
client_id:client2
client_secret:secret
grant_type:client_credentials
scope:sup_api
and we have got the following response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjM2ZWE2MGZlNGY2NDZkYjIxZjI0Y2ExNjEzZTBmMTgyIiwidHlwIjoiSldUIn0.eyJuYmYiOjE1MTk4OTM1MTYsImV4cCI6MTUxOTg5MzU2NiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NTAwMC9yZXNvdXJjZXMiLCJzdXBfYXBpIl0sImNsaWVudF9pZCI6ImNsaWVudDIiLCJzY29wZSI6WyJzdXBfYXBpIl19.cOznF6F6AL8onLZvvJaSX137P19k6doNa2BoJJTs6WY1LL47UOWoPhR7xIffQVSKyxGp4r-Z02kZrABjjyXzcdTaCR4538Pexep2sjlPobmKI0rfjR2apBSaMBVFXqDW-3VLTnMPyqicIBYjll5iS8YFGpUh0jZwq4rzNvYR4OooHssijQtkhpWxGzuokjKj8ZK1conySqEqorlaFJevY2x4jNlP3v0wpJ_6p77H4Lh12XENw4laGlrejtOkilnRaT7V8CclRGNsgPc81NLJhQZEp89cl37iQ1vLH74hCSs4MllO_eAZ_3Rmdan6QWUM1_zbcCEjGbXJM0QQ2qCpHw",
"expires_in": 3600,
"token_type": "Bearer"
}
But now, how we can test refresh tokens?
One way to do this is check if user still has access after the access token expiration time.
E.g.
At a high level this is what it would look like
Set access token lifetime to 1 minute
Run access test against API at 6 minute mark (there is a inbuilt delay when it actually expires the token)
You should assert that 401 will return, if it does then pass
Activate offline token
Run access test after 6 minute mark
Assert that you get non 401 response, if so then pass
It more testing of
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)
I've written a small IdentityServer demo server, following the examples in the documentation. I have the following TestUser:
new TestUser
{
SubjectId = "1",
Username = "Username",
Password = "password",
Claims = new List<Claim>()
{
new Claim(System.Security.Claims.ClaimTypes.Name, "Username"),
new Claim(System.Security.Claims.ClaimTypes.Email, "username#domain.com")
}
}
I get an access token using ResourceOwnerPassword flow. And I am authorized to access my API.
The problem is that when in my protected API I'm trying to get the user identity, the name property is returned as null, and I don't see the email claim. No matter what I do I always see the same 12 claims. The sub claim is the only one passed with the information I put in the Client object.
How can I populate the HttpContext.User.Identity.Name property and send additional claims/data about the user?
The reason probably is that you are not requesting the proper resources/scopes for your client.
You need to define an API resource with the claims you need in the access token.
e.g in Resources.cs you can add the claims to be included in all api2 scopes
new ApiResource
{
Name = "api2",
ApiSecrets =
{
new Secret("secret".Sha256())
},
UserClaims =
{
JwtClaimTypes.Name,
JwtClaimTypes.Email
},
Scopes =
{
new Scope()
{
Name = "api2.full_access",
DisplayName = "Full access to API 2",
},
new Scope
{
Name = "api2.read_only",
DisplayName = "Read only access to API 2"
}
}
}
Then you allow your resource owner client the access to those API resources.
e.g in client.cs
new Client
{
ClientId = "roclient",
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AllowOfflineAccess = true,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
"custom.profile",
"api1", "api2.read_only"
}
},
You can then request the scope in your roclient
client.RequestResourceOwnerPasswordAsync("bob", "bob", "api2.read_only", optional).Result
Post the access token to the API and you will get the claims you added to your API resource.
In the call to UseOpenIdConnectAuthentication, or wherever you're trying to use the token, make sure you set the TokenValidationParameters for the Name property to ClaimTypes.Name.
By default, the Name claim type is set to name (JwtClaimType.Name).
I'm new to Identity Server and am confused on the topic of Identity & Access tokens. I understand access tokens are meant to secure resources (i.e. web api) and that identity tokens are used to authenticate. However, whenever I call /connect/token I always receive an "access_token". Within the request I've asked for a client which has various scopes and claims.
new Client
{
ClientId = "Tetris",
ClientName = "Tetris Web Api",
AccessTokenLifetime = 60*60*24,
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
RequireClientSecret = false,
AllowedScopes = {"openid", "TetrisApi", "TetrisIdentity"}
}
public static IEnumerable<ApiResource> GetApiResources()
{
return new[]
{
new ApiResource("TetrisApi", "Tetris Web API", new[] { JwtClaimTypes.Name, JwtClaimTypes.Role, "module" })
};
}
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource
{
Name = "TetrisIdentity",
UserClaims =
new[]
{
JwtClaimTypes.Name,
JwtClaimTypes.Role,
JwtClaimTypes.GivenName,
JwtClaimTypes.FamilyName,
JwtClaimTypes.Email,
"module",
"module.permissions"
}
}
};
}
Below is a copy of postman:
Any thoughts? I didn't see an example in the Quickstarts that employs Identity Tokens.
Thanks!
The password grant type does not support identity tokens. See RFC6749.
The best you can do here is to use the access token to get claims for the user using the userinfo endpoint.
The recommendation is to use an interactive flow like implicit or hybrid for end-user authentication.
#leastprivilege 's answer is correct but instead of calling the userinfo endpoint, you also have the option of including the UserClaims you desire in your ApiResource definition.
At the moment you request new[] { JwtClaimTypes.Name, JwtClaimTypes.Role, "module" }, but if you changed that to include all the claims you (currently) define as part of the IdentityResources then those claims will also be available in the access_token.