63

I am setting up bearer token authentication in Web API 2, and I don't understand how (or where) the bearer token is being stored server-side. Here is the relevant code:

Startup:

public partial class Startup
{
    public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
    public static Func<UserManager<IdentityUser>> UserManagerFactory { get; set; }
    public static string PublicClientId { get; private set; }

    static Startup()
    {
        PublicClientId = "self";
        UserManagerFactory = () => new UserManager<IdentityUser>(new UserStore<IdentityUser>());
        OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/Token"),
            Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
            AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
            AllowInsecureHttp = true
        };
    }

    public void ConfigureAuth(IAppBuilder app)
    {
        // Enable the application to use a cookie to store information for the signed in user
        app.UseCookieAuthentication(new CookieAuthenticationOptions());

        // Use a cookie to temporarily store information about a user logging in with a third party login provider
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        app.UseOAuthBearerTokens(OAuthOptions);
    }
}

WebApiConfig:

public class WebApiConfig
{
    public static void ConfigureWebApi()
    {
        Register(GlobalConfiguration.Configuration);
    }

    public static void Register(HttpConfiguration http)
    {
        AuthUtil.ConfigureWebApiToUseOnlyBearerTokenAuthentication(http);
        http.Routes.MapHttpRoute("ActionApi", "api/{controller}/{action}", new {action = Actions.Default});
    }
}

AuthUtil:

public class AuthUtil
{
    public static string Token(string email)
    {
        var identity = new ClaimsIdentity(Startup.OAuthOptions.AuthenticationType);
        identity.AddClaim(new Claim(ClaimTypes.Name, email));
        var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
        var currentUtc = new SystemClock().UtcNow;
        ticket.Properties.IssuedUtc = currentUtc;
        ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
        var token = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
        return token;
    }

    public static void ConfigureWebApiToUseOnlyBearerTokenAuthentication(HttpConfiguration http)
    {
        http.SuppressDefaultHostAuthentication();
        http.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
    }
}

LoginController:

public class LoginController : ApiController
{
    ...

    public HttpResponseMessage Post([FromBody] LoginJson loginJson)
    {
        HttpResponseMessage loginResponse;
        if (/* is valid login */)
        {
            var accessToken = AuthUtil.Token(loginJson.email);
            loginResponse = /* HTTP response including accessToken */;
        }
        else
        {
            loginResponse = /* HTTP response with error */;
        }
        return loginResponse;
    }
}

Using the above code, I'm able to login and store the bearer token client-side in a cookie, and then make calls to controllers marked with [Authorize] and it lets me in.

My questions are:

  1. Where / how is the bearer token being stored server-side? It seems like this is hapenning through one of the OWIN calls but I can't tell where.

  2. Is it possible to persist the bearer tokens to a database server-side so that they can remain in place after a Web API server restart?

  3. If the answer to #2 is no, is there anyway for a client to maintain its bearer token and re-use it even after the Web API goes down and comes back up? While this may be rare in Production, it can happen quite often doing local testing.

dposada
  • 889
  • 1
  • 7
  • 15

4 Answers4

92
  1. They're not stored server side -- they're issued to the client and the client presents them on each call. They're verified because they're signed by the owin host's protection key. In SystemWeb hosting, that protection key is the machineKey setting from web.config.

  2. That's unnecessary, as long as the protection key the owin host uses doesn't change across server restarts.

  3. A client can hold onto a token for as long as the token is valid.

Brock Allen
  • 7,385
  • 19
  • 24
  • How does the bearer token works in farm environment where requests could hit on any server within the farm? How does the token get verified? – activebiz Jun 11 '14 at 12:21
  • 3
    As I said, the protection is via the host's protection which will be the machineKey in IIS. So to make the web farm work you'd need to sync the machineKey in web.config. – Brock Allen Jun 11 '14 at 23:25
  • I've searched for the machineKey in my project that is using OWIN, but it doesn't appear to be declared anywhere. Is there a default protection key that OWIN uses if machineKey isn't explicitly set, or am I just missing it somewhere? – EJay Dec 18 '14 at 15:16
  • 1
    Then you're getting the auto generated machine key. – Brock Allen Dec 18 '14 at 23:21
  • 1
    how do you refresh or extend this token generated with every call to the service from any client? Where the generated token remains the same and only the Expiry time is extended by the configured amount of time? – user20358 Apr 09 '15 at 14:31
  • 4
    Re answer 1: Just wondering if it is just a verification how does it validate the expiry date/time? And isn't each token different? – GayanSanjeewa Nov 04 '15 at 04:36
  • 1
    @BrockAllen - with Web Api 2, we are trying to figure out how to REMOVE the token from the client. In other words, we are simulating a `Logout` button for the client with the intention of deleting/invalidating the auth token. We are having a bit of difficulty on this one. Thanks. – bob.mazzo Mar 15 '16 at 15:27
  • 1
    @bob there is no way to do it for specific users when using just the plan OAuth2 bearer token pattern. What you need to do is implement the Refresh Token pattern on top of it in which your issue very short lived bearer tokens along side long lived refresh tokens. Refresh token IS stored server side (I use Redis for my refresh token stuff). Client on an interval just short of expiration requests a fresh token by calling the /Token endpoint with the refresh token and gets a new Access (bearer) token. Then, when you log off, you invalidate refresh token on server and access token will soon expire – Bon Jun 20 '16 at 18:20
  • @BrockAllen what if someone sniffs your token and use it for malicious purpose? if someone will have token ten he can access all data! how can we secure this thing – Saim Abdullah Dec 15 '17 at 11:31
3

For those who are looking for how to set web.config, here is a sample

<system.web>
<machineKey validation="HMACSHA256" validationKey="64-hex"
                 decryption="AES" decryptionKey="another-64-hex"/>
</system.web>

You need both validationKey and decriptionkey to make it work.

And here is how to generate keys https://msdn.microsoft.com/en-us/library/ms998288.aspx

Bruce Wang
  • 81
  • 2
2

To add to this, the token can be persisted server side using the SessionStore property of of CookieAuthenticationOptions. I wouldn't advocate doing this but it's there if your tokens become excessively large.

This is an IAuthenticationSessionStore so you could implement your own storage medium.

0

By default the token is not stored by the server. Only your client has it and is sending it through the authorization header to the server.

If you used the default template provided by Visual Studio, in the Startup ConfigureAuth method the following IAppBuilder extension is called: app.UseOAuthBearerTokens(OAuthOptions).

  • 2
    Downvoting. Answered more than 5 years after original accepted answer, only answers one of the posted questions, and offers nothing further than the accepted answer. – Ash Jan 07 '20 at 03:42