Обработка устаревших токенов обновления в ASP.NET Core - PullRequest
0 голосов
/ 05 сентября 2018

СМ. Ниже для кода, который решил эту проблему

Я пытаюсь найти лучший и наиболее эффективный способ обработки токена обновления, срок действия которого истек в ASP.NET Core 2.1.

Позвольте мне объяснить немного больше.

Я использую OAUTH2 и OIDC для запроса потоков разрешения кода авторизации (или гибридного потока с OIDC). Этот тип потока / предоставления дает мне доступ к AccessToken и RefreshToken (Код авторизации также, но это не для этого вопроса).

Токен доступа и токен обновления хранятся в ядре ASP.NET и могут быть получены с помощью HttpContext.GetTokenAsync("access_token"); и HttpContext.GetTokenAsync("refresh_token"); соответственно.

Я могу обновить access_token без проблем. Эта проблема вступает в игру, когда срок действия refresh_token истек, отозван или недействителен каким-либо образом.

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

Мой вопрос заключается в том, как добиться этого наилучшим и наиболее правильным методом. Я решил написать пользовательское промежуточное ПО, которое пытается обновить access_token, если срок его действия истек. Затем промежуточное ПО устанавливает новый токен в AuthenticationProperties для HttpContext, чтобы его можно было использовать любыми вызовами позже по каналу.

Если по какой-либо причине не удается обновить токен, мне нужно снова вызвать ChallengeAsync. Я звоню ChallengeAsync из промежуточного программного обеспечения.

Здесь я сталкиваюсь с интересным поведением. В большинстве случаев это работает, однако иногда я получаю 500 ошибок без полезной информации о том, что не работает. Похоже, что промежуточное ПО испытывает проблемы с попыткой вызова ChallengeAsync из промежуточного ПО, и, возможно, другое промежуточное ПО также пытается получить доступ к контексту.

Я не совсем уверен, что происходит. Я не совсем уверен, если это правильное место, чтобы поставить эту логику или нет. Может быть, я не должен иметь это в промежуточном программном обеспечении, может быть, где-то еще Возможно, Полли для HttpClient - лучшее место.

Я открыт для любых идей.

Спасибо за любую помощь, которую вы можете предоставить.

Code Solution, который работал для меня


Спасибо Mickaël Derriey за помощь и руководство (не забудьте посмотреть его ответ для получения дополнительной информации о контексте этого решения) Это решение, которое я придумал, и оно работает для я:

options.Events = new CookieAuthenticationEvents
{
    OnValidatePrincipal = context =>
    {
        //check to see if user is authenticated first
        if (context.Principal.Identity.IsAuthenticated)
        {
            //get the users tokens
            var tokens = context.Properties.GetTokens();
            var refreshToken = tokens.FirstOrDefault(t => t.Name == "refresh_token");
            var accessToken = tokens.FirstOrDefault(t => t.Name == "access_token");
            var exp = tokens.FirstOrDefault(t => t.Name == "expires_at");
            var expires = DateTime.Parse(exp.Value);
            //check to see if the token has expired
            if (expires < DateTime.Now)
            {
                //token is expired, let's attempt to renew
                var tokenEndpoint = "https://token.endpoint.server";
                var tokenClient = new TokenClient(tokenEndpoint, clientId, clientSecret);
                var tokenResponse = tokenClient.RequestRefreshTokenAsync(refreshToken.Value).Result;
                //check for error while renewing - any error will trigger a new login.
                if (tokenResponse.IsError)
                {
                    //reject Principal
                    context.RejectPrincipal();
                    return Task.CompletedTask;
                }
                //set new token values
                refreshToken.Value = tokenResponse.RefreshToken;
                accessToken.Value = tokenResponse.AccessToken;
                //set new expiration date
                var newExpires = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResponse.ExpiresIn);
                exp.Value = newExpires.ToString("o", CultureInfo.InvariantCulture);
                //set tokens in auth properties 
                context.Properties.StoreTokens(tokens);
                //trigger context to renew cookie with new token values
                context.ShouldRenew = true;
                return Task.CompletedTask;
            }
        }
        return Task.CompletedTask;
    }
};

1 Ответ

0 голосов
/ 05 сентября 2018

Токен доступа и токен обновления хранятся в ядре ASP.NET

Я думаю, важно отметить, что токены хранятся в файле cookie, который идентифицирует пользователя вашего приложения.

Теперь это мое мнение, но я не думаю, что пользовательское промежуточное ПО - это то место, где нужно обновлять токены. Причина этого заключается в том, что если вы успешно обновите токен, вам придется заменить существующий и отправить его обратно в браузер в виде нового файла cookie, который заменит существующий.

Вот почему я думаю, что наиболее подходящее место для этого - это когда cookie читается ASP.NET Core. Каждый механизм аутентификации выставляет несколько событий; для файлов cookie есть файл с именем ValidatePrincipal, который вызывается при каждом запросе после того, как файл cookie был прочитан, и из него была успешно десериализована личность.

public void ConfigureServices(ServiceCollection services)
{
    services
        .AddAuthentication()
        .AddCookies(new CookieAuthenticationOptions
        {
            Events = new CookieAuthenticationEvents
            {
                OnValidatePrincipal = context =>
                {
                    // context.Principal gives you access to the logged-in user
                    // context.Properties.GetTokens() gives you access to all the tokens

                    return Task.CompletedTask;
                }
            }
        });
}

Хорошая особенность этого подхода заключается в том, что если вам удастся обновить токен и сохранить его в AuthenticationProperties, переменная context, имеющая тип CookieValidatePrincipalContext, будет иметь свойство, называемое ShouldRenew . Если для этого свойства задано значение true, промежуточное программное обеспечение выдает новый файл cookie.

Если вы не можете обновить токен или обнаружите, что срок действия маркера обновления истек, и вы хотите запретить продвижение пользователя, в этом же классе есть метод RejectPrincipal, который инструктирует промежуточное ПО cookie обрабатывать запрос, как если бы он был анонимным.

Приятно то, что если ваше приложение MVC разрешает доступ к нему только аутентифицированным пользователям, MVC позаботится о выдаче ответа HTTP 401, который система аутентификации перехватит и превратит в вызов, а пользователь будет перенаправлен вернуться к провайдеру идентификации.

У меня есть некоторый код, который показывает, как это будет работать в репозитории mderriey/TokenRenewal на GitHub. Хотя цель иная, она показывает механизм использования этих событий.

...