Search code examples
c#asp.net-coretokenrefresh-tokenopeniddict

Do not receive refresh token with OpenIddict


I have a web api project based on .net core 2.0.

I followed pretty much the very good example on http://kevinchalet.com/2017/01/30/implementing-simple-token-authentication-in-aspnet-core-with-openiddict/.

The code that returns the SignIn() result for the auth. method looks like so:

if (request.IsPasswordGrantType())
{
    // (...)
    if (useraccount != null && useraccount.Failcount <= AppConstants.AuthMaxAllowedFailedLogin)
    {
        var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme, OpenIdConnectConstants.Claims.Name, OpenIdConnectConstants.Claims.Role);

        identity.AddClaim(OpenIdConnectConstants.Claims.Subject, AppConstants.AuthSubjectClaim, OpenIdConnectConstants.Destinations.AccessToken);
        identity.AddClaim(OpenIdConnectConstants.Claims.Name, useraccount.Username, OpenIdConnectConstants.Destinations.AccessToken);

        return SignIn(new ClaimsPrincipal(identity), OpenIdConnectServerDefaults.AuthenticationScheme);
    }
    // (...)
}

My startup code looks like so:

services.AddDbContext<DbContext>(options =>
{
    options.UseInMemoryDatabase(nameof(DbContext));
    options.UseOpenIddict();
});

services.AddOpenIddict(options =>
{
    options.AddEntityFrameworkCoreStores<DbContext>();
    options.AddMvcBinders();
    options.EnableTokenEndpoint(DcpConstants.ApiTokenRoute);
    options.AllowPasswordFlow();
    options.AllowRefreshTokenFlow();
    options.SetAccessTokenLifetime(TimeSpan.FromHours(1));
    options.SetRefreshTokenLifetime(TimeSpan.FromDays(1));
    options.DisableHttpsRequirement();
});

services.AddAuthentication(options =>
{
    options.DefaultScheme = OAuthValidationDefaults.AuthenticationScheme;
}).AddOAuthValidation();

Now, when I send the post request with the following params:

username: [email protected]
password: myPassword
grant_type: password
scope: openid profile offline_access

I only receive scope, token_type, access_token, expires_in and id_token and no refresh_token.

What am I missing?


Solution

  • Returning a refresh token with the password is definitely allowed by the OAuth2 specification and thus, fully supported by OpenIddict.

    For a refresh token to be returned by OpenIddict, you have to grant the special offline_access scope when calling SignIn. E.g:

    if (request.IsPasswordGrantType())
    {
        // (...)
        if (useraccount != null && useraccount.Failcount <= AppConstants.AuthMaxAllowedFailedLogin)
        {
            var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme, OpenIdConnectConstants.Claims.Name, OpenIdConnectConstants.Claims.Role);
    
            identity.AddClaim(OpenIdConnectConstants.Claims.Subject, AppConstants.AuthSubjectClaim, OpenIdConnectConstants.Destinations.AccessToken);
            identity.AddClaim(OpenIdConnectConstants.Claims.Name, useraccount.Username, OpenIdConnectConstants.Destinations.AccessToken);
    
            var ticket = new AuthenticationTicket(
                new ClaimsPrincipal(identity),
                new AuthenticationProperties(),
                OpenIdConnectServerDefaults.AuthenticationScheme);
    
            // You have to grant the 'offline_access' scope to allow
            // OpenIddict to return a refresh token to the caller.
            ticket.SetScopes(OpenIdConnectConstants.Scopes.OfflineAccess);
    
            return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
        }
        // (...)
    }
    

    Note that you'll also have to handle the grant_type=refresh_token requests in your controller. Here's an example using Identity: https://github.com/openiddict/openiddict-samples/blob/dev/samples/RefreshFlow/AuthorizationServer/Controllers/AuthorizationController.cs#L75-L109

    External edit by [Ingmar]: Updated code for newer version (in package tomware.OpenIddict.UI.Identity.Api version 1.5.0 - not the newest though, but without "ticket" and works for me ...)

        // (in your password grant handler, instead of var ticket:)
    
        var principal = new ClaimsPrincipal(identity);
        principal.SetScopes( <your scope(s)>, OpenIddictConstants.Scopes.OfflineAccess);
    
        ...
    
        return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);