I am using the Web Api 2 template that comes with Visual Studio 2013 has some OWIN middleware to do User Authentication and the likes of.
In the OAuthAuthorizationServerOptions
I noticed that the OAuth2 Server is setup to hand out tokens that expire in 14 days
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/api/token"),
Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) ,
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
This is not suitable for my latest project. I would like to hand out short lived bearer_tokens that can be refreshed using a refresh_token
I have done lots of googling and can't find anything helpful.
So this is how far I have managed to get. I have now reached the point of "WTF do I now".
I have written a RefreshTokenProvider
that implements IAuthenticationTokenProvider
as per the RefreshTokenProvider
property on OAuthAuthorizationServerOptions
class:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString();
_refreshTokens.TryAdd(guid, context.Ticket);
// hash??
context.SetToken(guid);
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (_refreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
// Now in my Startup.Auth.cs
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/api/token"),
Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) ,
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(2),
AllowInsecureHttp = true,
RefreshTokenProvider = new RefreshTokenProvider() // This is my test
};
So now when someone requests a bearer_token
I am now sending a refresh_token
, which is great.
So now how do I uses this refresh_token to get a new bearer_token
, presumably I need to send a request to my token endpoint with some specific HTTP Headers set?
Just thinking out loud as I type... Should I handle refresh_token expiration in my SimpleRefreshTokenProvider
? How would a client obtain a new refresh_token
?
I could really do with some reading material / documentation because I don't want to get this wrong and would like to follow some sort of standard.
This question is related to
c#
asp.net-web-api
oauth-2.0
asp.net-identity
owin
I don't think that you should be using an array to maintain tokens. Neither you need a guid as a token.
You can easily use context.SerializeTicket().
See my below code.
public class RefreshTokenProvider : IAuthenticationTokenProvider
{
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
Create(context);
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
Receive(context);
}
public void Create(AuthenticationTokenCreateContext context)
{
object inputs;
context.OwinContext.Environment.TryGetValue("Microsoft.Owin.Form#collection", out inputs);
var grantType = ((FormCollection)inputs)?.GetValues("grant_type");
var grant = grantType.FirstOrDefault();
if (grant == null || grant.Equals("refresh_token")) return;
context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(Constants.RefreshTokenExpiryInDays);
context.SetToken(context.SerializeTicket());
}
public void Receive(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
if (context.Ticket == null)
{
context.Response.StatusCode = 400;
context.Response.ContentType = "application/json";
context.Response.ReasonPhrase = "invalid token";
return;
}
if (context.Ticket.Properties.ExpiresUtc <= DateTime.UtcNow)
{
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
context.Response.ReasonPhrase = "unauthorized";
return;
}
context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(Constants.RefreshTokenExpiryInDays);
context.SetTicket(context.Ticket);
}
}
You need to implement RefreshTokenProvider. First create class for RefreshTokenProvider ie.
public class ApplicationRefreshTokenProvider : AuthenticationTokenProvider
{
public override void Create(AuthenticationTokenCreateContext context)
{
// Expiration time in seconds
int expire = 5*60;
context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddSeconds(expire));
context.SetToken(context.SerializeTicket());
}
public override void Receive(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
}
Then add instance to OAuthOptions.
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/authenticate"),
Provider = new ApplicationOAuthProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(expire),
RefreshTokenProvider = new ApplicationRefreshTokenProvider()
};
Freddy's answer helped me a lot to get this working. For the sake of completeness here's how you could implement hashing of the token:
private string ComputeHash(Guid input)
{
byte[] source = input.ToByteArray();
var encoder = new SHA256Managed();
byte[] encoded = encoder.ComputeHash(source);
return Convert.ToBase64String(encoded);
}
In CreateAsync
:
var guid = Guid.NewGuid();
...
_refreshTokens.TryAdd(ComputeHash(guid), refreshTokenTicket);
context.SetToken(guid.ToString());
ReceiveAsync
:
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
Guid token;
if (Guid.TryParse(context.Token, out token))
{
AuthenticationTicket ticket;
if (_refreshTokens.TryRemove(ComputeHash(token), out ticket))
{
context.SetTicket(ticket);
}
}
}
Source: Stackoverflow.com