Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practices refreshing access tokens in a MVC Core App containing both views and resources, using Identity Server 4

I have been reading up (and watching videos) on how to best implement access and refresh tokens when using a MVC Core application with a lot of ajax call in it. I think I got it right but just wanted to know if there is a better way of doing this. I will edit this post so It can serve as a reference for anyone looking for this information.

My setup: I have a MVC Core application with a lot of JavaScript. The JavaScripts are using ajax calls to retrieve json or call actions.

Since I don't want my users to be able to access my api using cookie authentication, I'm using app.Map to split my application in two parts. One where the users can access Views using the identity token and one that will require a access token. I'm also adding a cookie to hold the time when I need to refresh my access token.

Startup.cs (I removed the parts that's not importent)

  app.UseCookieAuthentication(new CookieAuthenticationOptions
  {
    AuthenticationScheme = "Cookies",
    AutomaticAuthenticate = true,
    ExpireTimeSpan = TimeSpan.FromMinutes(60)
  });

  JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

  var oidcOptions = new OpenIdConnectOptions
  {
    AuthenticationScheme = "oidc",
    SignInScheme = "Cookies",

    Authority = LoginServerUrl,
    RequireHttpsMetadata = false,
    ClientId = "MyApp",
    ClientSecret = "*****",
    ResponseType = "code id_token",
    SaveTokens = true,
    Events = new OpenIdConnectEvents()
    {
      OnTicketReceived = async notification =>
      {
        notification.Response.Cookies.Append("NextAccessTokenRefresh", DateTime.Now.AddMinutes(30).ToString());
        notification.Response.Cookies.Delete("AccessToken");
      },
    },

    TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
    {
      NameClaimType = JwtClaimTypes.Name,
      RoleClaimType = JwtClaimTypes.Role,
    },
  };

  oidcOptions.Scope.Clear();
  oidcOptions.Scope.Add("openid");
  oidcOptions.Scope.Add("roles");
  oidcOptions.Scope.Add("offline_access");

  app.UseOpenIdConnectAuthentication(oidcOptions);

  app.Map("/api", (context) =>
  {
    var bearerTokenOptions = new IdentityServerAuthenticationOptions
    {
      AuthenticationScheme = "Bearer",
      Authority = LoginServerUrl,,
      RequireHttpsMetadata = false,
      ScopeName = "MyApi",
      AutomaticAuthenticate = true
    };

    context.UseIdentityServerAuthentication(bearerTokenOptions);
    context.UseMvcWithDefaultRoute();
  });

All ajax calls to controller actions are done using the following url /api/[Controller]/[Action].

I do not want my api to be accessible using the identity token so I also add Authorize(ActiveAuthenticationSchemes = "Bearer") attribute to the controller action. So now my controller actions that are getting called by javascript looks like this:

[HttpPost, Authorize(ActiveAuthenticationSchemes = "Bearer")]
public async Task<JsonResult> DoStuff()
{
}

When a javascript needs to access a api resource, the controler first retrives the access token and injects it into the javascript using a custom javascript init method.

This C# method is responsible for refreshing and retriving the access cookie.

public async Task<string> GetAccessTokenAsync()
{
  var accessToken = _contextAccessor.HttpContext.Request.Cookies["AccessToken"];
  var nextAccessTokenRefresh = _contextAccessor.HttpContext.Request.Cookies["NextAccessTokenRefresh"];
  if (string.IsNullOrEmpty(nextAccessTokenRefresh) || string.IsNullOrEmpty(accessToken) || DateTime.Parse(nextAccessTokenRefresh) <= DateTime.Now)
  {
    var refreshToken = await _contextAccessor.HttpContext.Authentication.GetTokenAsync("refresh_token");
    var tokenClient = new TokenClient(_appSettings.LoginServerUrl + "/connect/token", _appSettings.LoginClientId, _appSettings.LoginClientSecret);
    var response = await tokenClient.RequestRefreshTokenAsync(refreshToken);
    accessToken = response.AccessToken;

    //Set cookies for next refresh
    _contextAccessor.HttpContext.Response.Cookies.Append("NextAccessTokenRefresh", DateTime.Now.AddMinutes(30).ToString());
    _contextAccessor.HttpContext.Response.Cookies.Append("AccessToken", response.AccessToken);
  }

  return accessToken;
}

On all my $.ajax I have added the following parameter:

beforeSend: function(xhr, settings) { xhr.setRequestHeader('Authorization','Bearer ' + accessToken); }

Thats it. The default access token expiration is one hour. I always refresh it after half that time.

Now to my questions:

  1. Can I improve my code in any way ?
  2. Do you see any security related risks doing it this way ?
  3. Can I retrieve the access token in the OnTicketReceived ?
like image 847
Per B Avatar asked Oct 01 '16 16:10

Per B


People also ask

What is the best way to store refresh token?

The authorization server can contain this risk by detecting refresh token reuse using refresh token rotation. If your application uses refresh token rotation, it can now store it in local storage or browser memory. You can use a service like Auth0 that supports token rotation.

When should refresh token be refreshed?

This means that even if an authorisation consent is, say, 90 days, the refresh token must be used within a 30-day window in order to “refresh” the access — at which point the refresh token lifetime is reset once again. This is all well and good.

How often should you refresh token?

The most secure option is for the authorization server to issue a new refresh token each time one is used. This is the recommendation in the latest Security Best Current Practice which enables authorization servers to detect if a refresh token is stolen.


1 Answers

Can I improve my code in any way ?

Your Startup.cs should be something like this(because Map works for only '/api' path. For more info see https://docs.asp.net/en/latest/fundamentals/middleware.html#run-map-and-use):

app.MapWhen(context => !context.Request.Path.Value.StartsWith("/api"), builder=>
{
    app.UseCookieAuthentication(options);
    ...
    app.UseOpenIdConnectAuthentication(oidcOptions);
    ....
});

app.MapWhen(context => context.Request.Path.Value.StartsWith("/api"), builder=>
{
    var bearerTokenOptions = new IdentityServerAuthenticationOptions
    {
      AuthenticationScheme = "Bearer",
      Authority = LoginServerUrl,,
      RequireHttpsMetadata = false,
      ScopeName = "MyApi",
      AutomaticAuthenticate = true
    };

    context.UseIdentityServerAuthentication(bearerTokenOptions);
    context.UseMvcWithDefaultRoute();
});

Second point, your cookie expire time is 60 minutes, in this case your refresh token's lifetime will be 60 minutes. I think this may be a problem.

Do you see any security related risks doing it this way ?

I haven't got enough experience for using refresh tokens, so i can't say that your implementation is secure or not. But i think refresh token(for your implementation) increases complexity also security risks(this is just my opinion and i am not a security expert). I would use only long lived access token with implicit flow(because of simplicity).

Can I retrieve the access token in the OnTicketReceived ?

Yes, you can:

OnTicketReceived = ctx =>
{
     var token = ctx.Ticket.Properties.GetTokenValue("access_token");
     ...
}
like image 109
adem caglin Avatar answered Oct 19 '22 09:10

adem caglin