I'm configuring AspNet.Identity for OAuth 2 with bearer tokens and I have seen multiple examples of implementing the OAuthAuthorizationServerProvider.GrantRefreshToken method, where the author demonstrates the ability to add a claim to the new ClaimsIdentity as seen below.
I am trying to understand this in the context of my single-server (i.e. my Web API project is both Authorization + Resource server) which I may split into separate servers at a later date, if necessary.
public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];
var currentClient = context.ClientId;
if (originalClient != currentClient)
{
context.SetError("invalid_clientId", "Refresh token is issued to a different clientId.");
return Task.FromResult<object>(null);
}
// Change auth ticket for refresh token requests
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
// CONSIDER: I don't know why you would add a claim here, but here's an example.
//var newClaim = newIdentity.Claims.Where(c => c.Type == "newClaim").FirstOrDefault();
//if (newClaim != null)
//{
// newIdentity.RemoveClaim(newClaim);
//}
//newIdentity.AddClaim(new Claim("newClaim", "newValue"));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
return Task.FromResult<object>(null);
}
The documentation states:
"The application must call context.Validated to instruct the Authorization Server middleware to issue an access token based on those claims and properties."
I don't understand this. I thought we were handing out a refresh token, not an access token.
Also, "The call to context.Validated may be given a different AuthenticationTicket or ClaimsIdentity in order to control which information flows from the refresh token to the access token."
I thought all of the claims were stored in my signed and encrypted access token, which is passed as Authorization: Bearer XXXXXX. However, I have a tenuous grasp on how ClaimsIdentity and AuthenticationTicket actually relate to anything in my OAuth 2.0 flow.
My best guess is that GrantRefreshToken needs to take the already authenticated and authorized identity (context.Ticket.Identity) and validate that a refresh token should be added to it by calling context.Validated.
Refresh Tokens in OAuth2 are just another type of grant: an alternative method of obtaining a new access_token.
For this reason the RefreshToken must be validated in order to emit a new access_token for the user.
When persisting the refresh token to the underlying storage (e.g. a database) the entire user identity must be stored along with it (usually using SerializeTicket() method of the AuthenticationTokenCreateContext object). This means that any change in the claims acquired during the first access_token generation will not be propagated to other access_tokens emissions using the RefreshToken grant by default (you will need to reload again those claims if you need to update them in the access_token).
I believe this is the main reason why many examples shows how to add/substitute claims in the new Identity inside the GrantRefreshToken method.
I will try to further clarify what usually happens when supporting the RefreshTokenGrant:
The user authenticates himself using any kind of supported grant (e.g. ResourceOwnerCredentials);
We create an Identity bounded to this specific user (we can add Claims at this point) and use it to create a new AuthenticationTicket (that we validate by calling Validated(ticket) on the specific context object) which will be used to create the access_token;
The framework generates a new refresh token calling CreateAsync on the provided IAuthenticationTokenProvider. Inside this method we have to retrieve the ticket and store it into some kind of persistence storage (e.g. a DB) along with an unique Id and some useful metadata. This Id is the refresh_token for the user point of view.
We return to the user the access_token (which contains the serialized claims for the user) and the refresh_token (which is just a reference).
After some time the user must authenticate again (e.g. the access_token expired), so he will send a request to the Token endpoint using the refresh_token.
We retrieve the refresh_token record from the persistent storage and deserialize the ticket that will be used to create a new Identity. This ticket contains all Claims that we added on the first authentication (this is almost an exact copy of the first access_token): if any of those Claims changed during the interval between the current moment and the first authentication (e.g. a new Role is added to the user, email has changed, etc.) we have now the chance to modify the new Identity and add/substitute those Claims, so that the new access_token will reflect the changes.
The flows continues by validating the ticket (as before) and generating a pair of access_token and refresh_token to send to the user.
Related
To add ADFS 3.0 authentication in our SPA we use the javascript sample and one (wsfed) external identityprovider, and we also add a local api for the SPA client
We also added a custom view to the login process, where the user could "Select WorkingContext" and we could set an additional claim.
Problem: How to add and retrive that additional claim?
Since we simply need the a few of the claim from ws federation, we've made a super simple callback where we just do the following (I'm answering the questions from the docs)
Handling the callback and signing in the user
inspect the identity returned by the external provider.
Yes, we get correct claims from wsfed
make a decision how you want to deal with that user. This might be different based on the fact if this is a new user or a returning user. New users might need additional steps and UI before they are allowed
in.
No additional steps required
probably create a new internal user account that is linked to the external provider.
No, we don't need the user, we just need the a few of the claims from wsfed, so we just return a TestUser based on the wsfed sub in FindUserFromExternalProvider
store the external claims that you want to keep.
Do we need to store the claims, will the claims not be embedded in the jwt token, and the token simply validated?
delete the temporary cookie
ok
sign-in the user
Here we would like to show a custom ui where the user should select a "workingcontext", and we could add the "workingcontext" as an additional claim.
Assuming the above is valid, how can we in step 6 add the extra claim?
await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.ClientId));
doesn't seem to give any ways to add claims.
This is how we try to pass the additional claims through the login process:
var isuser = new IdentityServerUser(user.SubjectId)
{
DisplayName = user.Username,
IdentityProvider = provider,
AdditionalClaims = additionalLocalClaims
};
await HttpContext.SignInAsync(isuser, localSignInProps);
We just need to implement the IProfileService, that's all.
I have already implemented my own IPersistedGrantStore called PostgresPersistedGrantStore that stores grant in my postgresql database and it works really great.
Now i want to move really forward and i want to get the refresh token from the key that is stored in my postgresql table. But from what i read it is not a proper refreshtoken but a hash to protect the refreshtoken. Is there a way to decrypt, read the refresh token from the key property, using maybe a fuction from the identityserver api?
I am trying to implement my own impersonation workflow, so it would be easy to login as any user using the latest refresh token that exists persisted in my db
A long time has passed since the question had been asked, but I think I'm sharing a relevant information.
Here is the method which is implemented at IdentityServer4.Stores.DefaultGrantStore<T> and actually creates the key for the refresh token.
protected virtual string GetHashedKey(string value)
{
return (value + KeySeparator + GrantType).Sha256();
}
Where
value is the actual value of the refresh token,
KeySeparator is a
constant string field defined at the same class, the
value is ":",
GrantType is 'refresh_token' in this particular case.
This method is being used while creating/validating the refresh token.
I think this information clearly states that there is no way to get the refresh token value by using the key.
Reference:
https://github.com/IdentityServer/IdentityServer4/blob/main/src/IdentityServer4/src/Stores/Default/DefaultGrantStore.cs#L77
Identity Server 4 has a build-in endpoint for this - <server_url>/Connect/Token.
You need to send a POST request to this endpoint, with x-www-form-urlencoded body type, which contains:
refresh_token: current refresh_token
client_id: the client that you are refreshing the token for
client_secret: the secret of the client
grant_type: refresh_token
It will give you back a "refreshed" access_token along with a new refresh_token.
The initial refresh_token you should have received once you have logged in. Have in mind - once refresh_token is used (to get a new access_token) it gets invalidated. This is why you are receiving a new with every request to the endpoint.
Here is some more info about the refresh_token itself.
And here - about the token endpoint.
Context:
I've implemented OpenIddict in my application, basing on GitHub readme. I currently use TokenEndpoint to log user in.
services.AddOpenIddict<ApplicationUser, UsersDbContext>().EnableTokenEndpoint("/api/Account/Login")
Although calling /api/Account/Info works and it returns token in response, I need to get some basic data about logged in user (username, email, account type). Response from token endpoint doesn't provide that.
I've found something like UserinfoEndpoint:
.EnableUserinfoEndpoint("/api/Account/Info")
But what I see after in http response is:
{
"sub": "ea2248b4-a[...]70757de60fd",
"iss": "http://localhost:59381/"
}
This should return me some Claims. As it doesn't return anything, I assume that no Identity Claims were created during token generation.
What I need to know in a nutshell:
Is using Token Endpoint correct way to log in user?
Do I need to generate Claims by myself?
Can I control Claims by myself and how?
How to make some Claims visible through UserInfoEndpoint?
Is using Token Endpoint correct way to log in user?
Yep.
Do I need to generate Claims by myself?
The userinfo endpoint simply exposes the tokens stored in the access token (which is something that may change in the future).
Can I control Claims by myself and how?
How to make some Claims visible through UserInfoEndpoint?
To allow the userinfo endpoint to return more claims, you'll have to request more scopes. Read http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims for more information.
In a future version, we may allow you to return custom claims, but it's not currently supported.
Question
How does User.Identity.GetUserId() finds the current user's Id?
Does it find the user Id from the Cookies, or does it query the database? Or any other methods?
Problem
For some reason, User.Identity.GetUserId() returns null when I add a valid Bearer Token to my Http request header and send the request to my controller's endpoint:
// MVC Controller Action Method
[Authorize]
public HttpResponseMessage(UserInfoViewModel model)
{
// Request passes the Authorization filter since a valid oauth token
// is attached to the request header.
string userId = User.Identity.GetUserId();
// However, userId is null!
// Other stuff...
}
How does User.Identity.GetUserId() finds the current user's Id?
ClaimTypes.NameIdentifier is the claim used by the function User.Identity.GetUserId()
You would need to add the claim in your authorization code,
identity.AddClaim(ClaimTypes.NameIdentifier, user.Id);
identity is of type ClaimIdentity.
When the user is logged into your app, the server, using ASP.NET Identity, validates your user using DB and creates a valid token that returns to the UI. This token will be valid to its expiration and has inside all information needed to authenticate and authorize the user, including the user's Id. Next calls from client side to server side must be done using this token in the http request header, but server will not call the DB again, because ASP.NET identity knows how to decrypt the token and get all the information of your user.
The use of cookies is only a way to store the token in the client side. As I commented above, you have to send the token on the next requests after the login, so you can store this token in cookies or in Session Storage in your browser.
First, make sure you're not allowing for non-authenticated users.
After that, you want to parse Bearer tokens you have to configure it.
You're going to the need this package Microsoft.Owin.Security.OAuth
And at startup if have to configure ASP.NET Identity to use Bearer Authentication with:
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions {
// your options;
});
Probably on your StartupAuth.cs file
I'm working on a Web Application that allows user to connect AdWords account.
1) So once user connected their AW account I am getting token from AW API that includes an access token and a refresh token. So far so good, it's just a typical OAuth2 process.
2) Next time another user connects same AW account, AW would not provide me with a refresh token, assuming I have it stored somewhere, which is expected.
So here is the problem, there doesn't seem to be a way to get user information without the refresh token, meaning I can't identify the user to retrieve the refresh token.
I'm using .NET library (Google.Apis.Analytics.v3) and it doesn't allow me to request customer information without providing the refresh token...
Here is the sample code:
var tokenResponse = await adWordsService.ExchangeCodeForTokenAsync(code, redirectUri);
var adWordsUser = adWordsService.GetAdWordsUser();
var customerService = (CustomerService)adWordsUser.GetService(AdWordsService.v201502.CustomerService);
var customer = customerService.get();
adWordsService is just a wrapper around the API.
so when I execute this line var customer = customerService.get() I get a following error:
ArgumentNullException: Value cannot be null.
Parameter name: AdWords API requires a developer token. If you don't have one, you can refer to the instructions at https://developers.google.com/adwords/api/docs/signingup to get one.
Google.Api.Ads.AdWords.Lib.AdWordsSoapClient.InitForCall(String methodName, Object[] parameters)
Developer token is there and so are all the client IDs.
If I add this line adWordsUser.Config.AuthToken = tokenResponse.AccessToken; before making the call, it complains about the refresh token.
ArgumentNullException: Value cannot be null.
Parameter name: Looks like your application is not configured to use OAuth2 properly. Required OAuth2 parameter RefreshToken is missing. You may run Common\Utils\OAuth2TokenGenerator.cs to generate a default OAuth2 configuration.
Google.Api.Ads.Common.Lib.OAuth2ProviderBase.ValidateOAuth2Parameter(String propertyName, String propertyValue)
So the question is, how does one acquire user information (in this case customerId, according to this article https://developers.google.com/adwords/api/docs/guides/optimizing-oauth2-requests#authenticating_multiple_accounts) during authentication for the purpose of storing the refresh token?
So I just found the problem is in the adWordsService. When the AdWordsUser is created it isn't assigned the updated authentication provider which contains the latest authentication token. Once this authentication provider is added the AdWordsUser doesn't try to refresh the token since the token hasn't expired yet, thus there is no need for the refresh token.