Как создать токен обновления с помощью внешнего провайдера?
Я искал через Интернет и не смог найти решение моей проблемы. Я использую OAuth в своем приложении. Я использую ASP.NET Web API 2 и Owin. Сценарий заключается в том, что после запроса пользователя на конечную точку токена он или она получит токен доступа вместе с токеном обновления для создания нового токена доступа. У меня есть класс, который помогает мне генерировать токен обновления. Вот он:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var refreshTokenId = Guid.NewGuid().ToString("n");
using (AuthRepository _repo = new AuthRepository())
{
var refreshTokenLifeTime = context.OwinContext.Get<string> ("as:clientRefreshTokenLifeTime");
var token = new RefreshToken()
{
Id = Helper.GetHash(refreshTokenId),
ClientId = clientid,
Subject = context.Ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(15)
};
context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
context.Ticket.Properties.ExpiresUtc = token.ExpiresUtc;
token.ProtectedTicket = context.SerializeTicket();
var result = await _repo.AddRefreshToken(token);
if (result)
{
context.SetToken(refreshTokenId);
}
}
}
// this method will be used to generate Access Token using the Refresh Token
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
string hashedTokenId = Helper.GetHash(context.Token);
using (AuthRepository _repo = new AuthRepository())
{
var refreshToken = await _repo.FindRefreshToken(hashedTokenId);
if (refreshToken != null )
{
//Get protectedTicket from refreshToken class
context.DeserializeTicket(refreshToken.ProtectedTicket);
// one refresh token per user and client
var result = await _repo.RemoveRefreshToken(hashedTokenId);
}
}
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
теперь я разрешаю своим пользователям регистрироваться через facebook. Как только пользователь зарегистрируется в facebook, я создаю токен доступа и передаю его ему. Должен ли я генерировать токен обновления? Что-то приходит мне на ум, это создать длинный токен доступа, как в один день, а затем этот пользователь снова должен войти в систему с facebook. Но если я не хочу этого делать, я могу предоставить клиенту токен обновления, и он может использовать его для обновления созданного токена доступа и получения нового. Как создать токен обновления и прикрепить его к ответу, когда кто-то зарегистрируется или заходит в систему с помощью facebook или извне?
Вот мой внешний API регистрации
public class AccountController : ApiController
{
[AllowAnonymous]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var accessTokenResponse = GenerateLocalAccessTokenResponse(model.UserName);
return Ok(accessTokenResponse);
}
}
//Закрытый метод для создания токена доступа
private JObject GenerateLocalAccessTokenResponse(string userName)
{
var tokenExpiration = TimeSpan.FromDays(1);
ClaimsIdentity identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
identity.AddClaim(new Claim("role", "user"));
var props = new AuthenticationProperties()
{
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.Add(tokenExpiration),
};
var ticket = new AuthenticationTicket(identity, props);
var accessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
JObject tokenResponse = new JObject(
new JProperty("userName", userName),
new JProperty("access_token", accessToken),
// Here is what I need
new JProperty("resfresh_token", GetRefreshToken()),
new JProperty("token_type", "bearer"),
new JProperty("refresh_token",refreshToken),
new JProperty("expires_in", tokenExpiration.TotalSeconds.ToString()),
new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())
);
return tokenResponse;
}
Ответы
Ответ 1
Я потратил много времени, чтобы найти ответ на этот вопрос. Поэтому я рад помочь вам.
1) Измените метод ExternalLogin.
Обычно это выглядит так:
if (hasRegistered)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
}
Теперь, на самом деле, нужно добавить refresh_token.
Метод будет выглядеть следующим образом:
if (hasRegistered)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
// ADD THIS PART
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context =
new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
Request.GetOwinContext(),
Startup.OAuthOptions.AccessTokenFormat, ticket);
await Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(context);
properties.Dictionary.Add("refresh_token", context.Token);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
}
Теперь токен refrehs будет сгенерирован.
2) Существует проблема использования базового контекста. СериализоватьТикет в SimpleRefreshTokenProvider CreateAsync.
Сообщение из Бит технологии
Кажется, в методе ReceiveAsync контекст .DeserializeTicket не возвращая аутентификационный билет вообще во внешнем случае входа. Когда я смотрю на свойство context.Ticket, после этого вызываем его значение null. Сравнивая это с локальным логином, метод DeserializeTicket устанавливает свойство context.Ticket в AuthenticationTicket. Итак тайна теперь, как происходит DeserializeTicket ведет себя по-разному в два потока. Создана защищенная строка билета в базе данных в том же методе CreateAsync, отличающемся только тем, что я называю это метод вручную в GenerateLocalAccessTokenResponse, против Owin middlware, называя это нормально... И ни SerializeTicket, ни DeserializeTicket выдает ошибку...
Итак, вам нужно использовать Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer для поиска и десериализации билета.
Это будет выглядеть так:
Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer
= new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer();
token.ProtectedTicket = System.Text.Encoding.Default.GetString(serializer.Serialize(context.Ticket));
вместо:
token.ProtectedTicket = context.SerializeTicket();
И для метода ReceiveAsync:
Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer = new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer();
context.SetTicket(serializer.Deserialize(System.Text.Encoding.Default.GetBytes(refreshToken.ProtectedTicket)));
вместо:
context.DeserializeTicket(refreshToken.ProtectedTicket);
3) Теперь вам нужно добавить refresh_token в ответ метода ExternalLogin.
Переопределите AuthorizationEndpointResponse в OAuthAuthorizationServerProvider. Что-то вроде этого:
public override Task AuthorizationEndpointResponse(OAuthAuthorizationEndpointResponseContext context)
{
var refreshToken = context.OwinContext.Authentication.AuthenticationResponseGrant.Properties.Dictionary["refresh_token"];
if (!string.IsNullOrEmpty(refreshToken))
{
context.AdditionalResponseParameters.Add("refresh_token", refreshToken);
}
return base.AuthorizationEndpointResponse(context);
}
Итак... вот и все! Теперь, вызывая метод ExternalLogin, вы получаете URL:
https://localhost:44301/Account/ExternalLoginCallback?access_token=ACCESS_TOKEN&token_type=bearer&expires_in=300&state=STATE&refresh_token=TICKET&returnUrl=URL
Я надеюсь, что это поможет)
Ответ 2
@giraffe и другие offcourse
Несколько замечаний. Там нет необходимости использовать пользовательский tickerserializer.
Следующая строка:
Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context =
new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
Request.GetOwinContext(),
Startup.OAuthOptions.AccessTokenFormat, ticket);
Как используется токенформат: Startup.OAuthOptions.AccessTokenFormat
. Поскольку мы хотим предоставить refeshtoken, это необходимо изменить следующим образом: Startup.OAuthOptions.RefreshTokenFormat
В противном случае, если вы хотите получить новый accesstoken и обновить refreshtoken (grant_type = refresh_token & refresh_token =......), десериализатор /unprotector завершится с ошибкой. Так как он использует неверные целые ключевые слова на этапе дешифрования.
Ответ 3
Наконец нашел решение для моей проблемы.
Прежде всего, если вы ВСЕГДА встречаете какие-либо проблемы с OWIN, и вы не можете понять, что происходит не так, я советую вам просто включить отрисовку символов и отладить его. Большое объяснение можно найти здесь:
http://www.symbolsource.org/Public/Home/VisualStudio
Моя ошибка просто заключалась в том, что я вычислял неправильный ExiresUtc при использовании внешних поставщиков входа. Так что мой refreshtoken в основном всегда истек сразу.
Если вы используете токены обновления, посмотрите на статью этого блога:
http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/
Чтобы заставить его работать с токенами обновления для внешних поставщиков, вам необходимо установить два параметра ( "как: clientAllowedOrigin" и "as: clientRefreshTokenLifeTime" ) в контекст
поэтому вместо
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
var context = new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
Request.GetOwinContext(),
Startup.OAuthOptions.AccessTokenFormat, ticket);
await Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(context);
properties.Dictionary.Add("refresh_token", context.Token);
вам нужно сначала получить клиента и задать параметры контекста
// retrieve client from database
var client = authRepository.FindClient(client_id);
// only generate refresh token if client is registered
if (client != null)
{
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
var context = new AuthenticationTokenCreateContext(Request.GetOwinContext(), AuthConfig.OAuthOptions.RefreshTokenFormat, ticket);
// Set this two context parameters or it won't work!!
context.OwinContext.Set("as:clientAllowedOrigin", client.AllowedOrigin);
context.OwinContext.Set("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());
await AuthConfig.OAuthOptions.RefreshTokenProvider.CreateAsync(context);
properties.Dictionary.Add("refresh_token", context.Token);
}