Do not receive refresh token with OpenIddict
Asked Answered
C

2

6

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?

Compressor answered 10/10, 2017 at 17:29 Comment(0)
F
11

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);
Ftlb answered 10/10, 2017 at 20:47 Comment(3)
Hi Kevin, I just wondered if you could provide an updated example of this, as the link is dead and the AuthenticationTicket object model is since been updated and thus does not have a SetScopes method. Thanks for your time.Twelvemonth
I see... "OpenIddict 3.0 no longer uses the AuthenticationTicket type provided by ASP.NET Core. Instead, everything is now stored in the ClaimsPrincipal instance." - I will look into this...Twelvemonth
To do this now, you will need to make sure that the PasswordTokenRequest scope is set to Scope.OfflineAccess. You will also need to make sure that this is OfflineScope is also set on the UserPrinciple. When you return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); via the normal Password flow route, you will have the refresh token returned.Twelvemonth
V
-1

options.AllowPasswordFlow();

Refresh Token cannot be used with Password flow, as the user is never redirected to login at Auth Server in this flow and so can’t directly authorize the application:

If the application uses the username-password OAuth authentication flow, no refresh token is issued, as the user cannot authorize the application in this flow. If the access token expires, the application using username-password OAuth flow must re-authenticate the user.

Violent answered 10/10, 2017 at 18:58 Comment(2)
It's worth noting that while Salesforce decided they didn't want to return a refresh token for password flow requests, this is absolutely not prohibited by the OAuth2 specification: If the access token request is valid and authorized, the authorization server issues an access token and optional refresh token tools.ietf.org/html/rfc6749#section-4.3.3Fluorosis
FWIW, I personally think that not returning a refresh token with the password flow is a terrible mistake, as it encourages client applications to work around this limitation by storing the username/password to retrieve new access tokens when they need to without having to ask the user his/her credentials again and again (which is fundamentally bad).Fluorosis

© 2022 - 2024 — McMap. All rights reserved.