I'm trying to set up Thinktecture's Identity Server 3, but I can't seem to get it to return a refresh token when exchanging an authorization code (or when using the ResourceOwner flow, but I'm going to focus on the authorization code as it's more important to me right now). I get back access tokens and can use them to authenticate just fine, but it doesn't seem to even be generating the refresh tokens that I'm expecting to get back. Is there anything special that I need to do to get Identity Server to return refresh tokens?
I've looked through the documentation, but haven't seen anything that I've set up wrong, and the only thing on their page on refresh tokens that I'm not doing is explicitly requesting the "offline_access" scope when sending the user there for authentication, because whenever I try I get an "invalid scope" error. Therefore, I'm taking Thinktecture's phrasing of "Request the offline_access scope (via code or resource owner flow)" to mean that the offline_access scope is something automatically requested based on the flow you're using.
I've been trying to follow their sample applications (And the source code for the existing Owin Middleware from the Katana Project) as best I can, and my setup is as follows:
var client = new Client() { ClientId = "SomeId", ClientName = "Client with Authentication Code Flow", RequireConsent = false, //Setting this to true didn't help Flow = Flows.AuthorizationCode, ClientSecrets = new List() { new ClientSecret("secret") }, RedirectUris = new List() { "localhost:/specific-redirect-path" } };
var authorizationEndpoint = AuthorizationEndpointBase + "?client_id=" + Uri.EscapeDataString(Options.ClientId) + "&scope=Default" + "&response_type=code" + "&redirect_uri=" + Uri.EscapeDataString(redirectUri) + "&state=" + Uri.EscapeDataString(state); Response.Redirect(authorizationEndpoint);where "Default" is a scope I created.
IReadableStringCollection query = Request.Query; string code = getValueFromQueryString("code", query); var tokenRequestParameters = new List>() { new KeyValuePair("client_id", Options.ClientId), new KeyValuePair("redirect_uri", GenerateRedirectUri()), new KeyValuePair("client_secret", Options.ClientSecret), new KeyValuePair("code", code), new KeyValuePair("grant_type", "authorization_code"), }; var requestContent = new FormUrlEncodedContent(tokenRequestParameters); HttpResponseMessage response = await _httpClient.PostAsync(TokenEndpoint, requestContent, Request.CallCancelled); response.EnsureSuccessStatusCode(); string oauthTokenResponse = await response.Content.ReadAsStringAsync();
When I make the call to the token endpoint, my logging on Identity Server displays the following (after the validation of the authorization code):
iisexpress.exe Information: 0 : [Thinktecture.IdentityServer.Core.Validation.TokenRequestValidator]: 7/13/2015 1:44:07 PM +00:00 -- Token request validation success { "ClientId": "SomeId", "ClientName": "Client with Authentication Code Flow", "GrantType": "authorization_code", "AuthorizationCode": "f8f795e649044067ebd96a341c5af8c3" } iisexpress.exe Information: 0 : [Thinktecture.IdentityServer.Core.ResponseHandling.TokenResponseGenerator]: 7/13/2015 1:44:07 PM +00:00 -- Creating token response iisexpress.exe Information: 0 : [Thinktecture.IdentityServer.Core.ResponseHandling.TokenResponseGenerator]: 7/13/2015 1:44:07 PM +00:00 -- Processing authorization code request Debug: [Thinktecture.IdentityServer.Core.Services.Default.DefaultTokenService]: 7/13/2015 1:44:07 PM +00:00 -- Creating access token Debug: [Thinktecture.IdentityServer.Core.Services.Default.DefaultTokenService]: 7/13/2015 1:44:07 PM +00:00 -- Creating reference access token iisexpress.exe Information: 0 : [Thinktecture.IdentityServer.Core.Endpoints.TokenEndpointController]: 7/13/2015 1:44:07 PM +00:00 -- End token request iisexpress.exe Information: 0 : [Thinktecture.IdentityServer.Core.Results.TokenResult]: 7/13/2015 1:44:07 PM +00:00 -- Returning token response.
I'm not sure what else would be pertinent, so I'll provide more information as needed.
You do have to explicitly ask for 'offline_access' in your request. Separate the other scopes you are requesting with a space. (In my examples below I am replacing 'Default' with 'MyApi' to be clear that we are talking about a scope defined by your app.)
&scope=MyApi offline_access
However, you must also grant that client the right to get refresh tokens, it doesn't just happen based on the flow you pick:
var client = new Client()
{
... //All the stuff you were doing before
ScopeRestrictions = new List<string>
{
"MyApi",
StandardScopes.OfflineAccess.Name, //"offline_access" -for refresh tokens
//Other commonly requested scopes:
//StandardScopes.OpenId.Name, //"openid"
//StandardScopes.Email.Name, //"email"
},
}
You may need to add 'offline_access' to your scope store as well. The scope store is the list of scopes that Identity Server knows about. Your question doesn't mention how your scope store is set up in your project, so you may already have it. But if the above doesn't immediately work for you, you may want to look around for code like this in the example you're working from and add OfflineAccess.
var scopeStore = new InMemoryScopeStore(new Scope[]{
StandardScopes.OpenId,
StandardScopes.Profile,
StandardScopes.Email,
StandardScopes.OfflineAccess, //<--- ensure this is here to allow refresh tokens
new Scope{
Enabled = true,
Name = "MyApi"
},
}
Add offline_access value in scope while sending token request
new Client
{
ClientId = "ro.angular",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Address,
"api1",
IdentityServerConstants.StandardScopes.OfflineAccess
},
AllowOfflineAccess = true,
RefreshTokenUsage = TokenUsage.ReUse,
RefreshTokenExpiration = TokenExpiration.Sliding
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With