Обновить токен с помощью аутентификации токена на предъявителя на asp.net vnext

У меня есть приложение angularJS, которое использует asp.net vnext, который аутентифицируется с помощью JwtBearerAuthentication. Для аутентификации приложения я использую https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server. При входе в систему я получаю ответ json, содержащий access_token, который я могу использовать для авторизованных запросов. Я хотел бы также получить токен обновления. Как это возможно?

Startup.cs

public void Configure(IApplicationBuilder app) {
    app.UseJwtBearerAuthentication(options => {
        options.AutomaticAuthenticate = true;
        options.AutomaticChallenge = true;
        options.TokenValidationParameters.ValidateAudience = false;
        options.Authority = Configuration.Get<string>("OAuth:Authority");
        options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
            metadataAddress: options.Authority + ".well-known/openid-configuration",
            configRetriever: new OpenIdConnectConfigurationRetriever(),
            docRetriever: new HttpDocumentRetriever() { RequireHttps = false });
    });

    app.UseOpenIdConnectServer(configuration => {
        configuration.Issuer = new Uri(Configuration.Get<string>("OpenId:Issuer"));
        configuration.AllowInsecureHttp = true;
        configuration.AuthorizationEndpointPath = PathString.Empty;
        configuration.AuthenticationScheme = OpenIdConnectServerDefaults.AuthenticationScheme;
        configuration.Provider = new AuthorizationProvider();
    });
}

AuthorizationProvider.cs

public class AuthorizationProvider : OpenIdConnectServerProvider {
    public override Task ValidateClientAuthentication(ValidateClientAuthenticationContext context) {
        context.Skipped();

        return Task.FromResult<object>(null);
    }

    public override Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsContext context) {
        string username = context.UserName;
        string password = context.Password;

        UserManager<ApplicationUser> userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>();
        ApplicationUser user = userManager.FindByNameAsync(username).Result;

        if (userManager.CheckPasswordAsync(user, password).Result) {
            ClaimsIdentity identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
            identity.AddClaim(ClaimTypes.Name, username, "token id_token");

            List<string> roles = userManager.GetRolesAsync(user).Result.ToList();
            foreach (string role in roles) {
                identity.AddClaim(ClaimTypes.Role, role, "token id_token");
            }

            ClaimsPrincipal principal = new ClaimsPrincipal(identity);
            context.Validated(principal);
        } else {
            context.Rejected("invalid credentials");
        }

        return Task.FromResult<object>(null);
    }
}

Код входа в AngularJS

$http({
    method: 'POST',
    url: 'connect/token',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    },
    data: $.param({
        grant_type: 'password',
        username: email,
        password: password
    })
}).then(function (response) {
    if (response.status == 200) {
        var token = response.data.access_token;
        localStorage.setItem('token', token);
    }
});

2 ответа

Решение

В отличие от OAuthAuthorizationServerMiddlewareASOS предлагает встроенную поддержку для обновления токенов: для этого вам не нужно создавать свой собственный поставщик токенов.

Обратите внимание, что начиная с бета-версии ASOS (выпущенной в октябре 2015 года), теперь вы должны спросить и предоставить offline_access область для получения токена обновления, как рекомендовано спецификациями OpenID Connect: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/issues/128

Вам нужно будет обновить свой GrantResourceOwnerCredentials чтобы позволить ASOS выдать токен обновления вашему клиентскому приложению:

public override async Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsContext context) {
    string username = context.UserName;
    string password = context.Password;

    UserManager<ApplicationUser> userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>();
    ApplicationUser user = await userManager.FindByNameAsync(username);

    if (await userManager.CheckPasswordAsync(user, password)) {
        ClaimsIdentity identity = new ClaimsIdentity(
            context.Options.AuthenticationScheme);

        identity.AddClaim(ClaimTypes.Name, username,
            OpenIdConnectConstants.Destinations.AccessToken,
            OpenIdConnectConstants.Destinations.IdentityToken);

        foreach (string role in await userManager.GetRolesAsync(user)) {
            identity.AddClaim(ClaimTypes.Role, role,
                OpenIdConnectConstants.Destinations.AccessToken,
                OpenIdConnectConstants.Destinations.IdentityToken);
        }

        AuthenticationTicket ticket = new AuthenticationTicket(
            new ClaimsPrincipal(identity),
            new AuthenticationProperties(),
            context.Options.AuthenticationScheme);

        // Call SetResources with the list of resource servers
        // the access token should be issued for.
        ticket.SetResources("resource_server_1");

        // Only grant the "offline_access" scope
        // if it was requested by the client application:
        List<string> scopes = new List<string>();
        if (context.Request.HasScope("offline_access")) {
            scopes.Add("offline_access");
        }

        // Call SetScopes with the list of scopes you want to grant.
        ticket.SetScopes(scopes);

        context.Validate(ticket);
    } else {
        context.Reject("invalid credentials");
    }

    return Task.FromResult(0);
}

... и ваш угловой код для указания scope параметр:

$http({
    method: 'POST',
    url: 'connect/token',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    },
    data: $.param({
        grant_type: 'password',
        username: email,
        password: password,
        scope: 'offline_access'
    })
}).then(function (response) {
    if (response.status == 200) {
        var token = response.data.access_token;
        var refreshToken = response.data.refresh_token;
        localStorage.setItem('token', token);
        localStorage.setItem('refresh_token', refreshToken);
    }
});

Чтобы получить новый токен доступа, используйте refresh_token предоставить:

$http({
    method: 'POST',
    url: 'connect/token',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    },
    data: $.param({
        grant_type: 'refresh_token',
        refresh_token: refreshToken
    })
}).then(function (response) {
    if (response.status == 200) {
        var token = response.data.access_token;
        localStorage.setItem('token', token);
    }
});

Создайте свой поставщик токенов обновления и включите его в параметры аутентификации. Здесь вы можете найти полный пример того, как он используется в аналогичной среде:

http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/

Другие вопросы по тегам