Session Token Support for ASP.NET Web API

Disclaimer: This is an experimental feature I added to Thinktecture.IdentityModel.45 (and will soon be back-ported to 4.0) to gather some feedback. It is completely turned off by default.

The idea is simple – the authentication library now allows swapping an arbitrary supported credential with a (long lived) session token.

I started to think about this feature after I got this question via email:

“In case you’re dealing with JavaScript clients + Basic Auth: how do you persist credentials on the client?”

Definitely storing any kinds of passwords yourself is an anti-pattern. Protocols like WS-Federation, OpenID, OAuth2 and friends are going a long way to make sure you only type in passwords where they belong to. But let’s say you don’t have that infrastructure, what would be a pragmatic way to solve the above problem?

With the session token support you could do it like this:

  • Use an arbitrary supported credential (e.g. a password) to request a session token. The session token endpoint is built right into the authentication handler.
  • The session token response contains the token itself and an expiration time.
  • Afterwards throw away the bootstrap credential and store the token instead.
  • When the token has expired, the user has to re-authenticate to obtain a new token. Then start over.

This is very similar to e.g. the Google IDs long lived authentication cookie. They establish an SSO session for a limited amount of time (approx. 2 weeks), then you have to enter your password again.

This is how it works in C# (JS sample to follow later):

Request the Session Token
Populate the HTTP request with some credential that is supported by the service, e.g. Basic Authentication:

var client = new HttpClient { BaseAddress = _baseAddress };

client.DefaultRequestHeaders.Authorization =

    new BasicAuthenticationHeaderValue(“alice”, “alice”);

 

var response = client.GetAsync(“token”).Result;

response.EnsureSuccessStatusCode();

 

Parse the Response
The token response is a OAuth2 access token response message (without the refresh token and token type). It contains a token and the expiration time of the token.

The token is btw a symmetrically signed JWT.

var tokenResponse = response.Content.ReadAsStringAsync().Result;

var json = JObject.Parse(tokenResponse);

var token = json[“access_token”].ToString();

 

You would then store that token and keep track of its expiration time.

Send the token
Afterwards you put the token on the Authorization header and use it to authenticate subsequent requests.

client = new HttpClient { BaseAddress = _baseAddress };

client.DefaultRequestHeaders.Authorization =
 
new AuthenticationHeaderValue(“Session”, token);

 

response = client.GetAsync(“identity”).Result;

response.EnsureSuccessStatusCode();

 

On the service side, there are various configuration options, like lifetime, key material, end point name etc. To play around, this should be enough:

var config = new AuthenticationConfiguration

{

    DefaultAuthenticationScheme = “Basic”,

    EnableSessionToken = true

};

 

Note
We had an internal discussion here if this feature isn’t kind of rebuilding WS-SecureConversation which led to many problems in WCF – and was mostly turned off.

I think the main problem of that WCF feature was not the protocol itself, but the way it was implemented:

  • It was coupled with the notion of a transport session.
  • It was too implicit (aka black magic)
  • It used server affinity by default.
  • You didn’t have a choice, either force sessions or not.

Since the proposed session token feature here is completely optional, can be combined with “standard” authentication *and* follows the same semantics as you would use for an external token service, I hope this is a better solution to the problem than WCF’ establishSecurityContext.

This entry was posted in IdentityModel, WebAPI. Bookmark the permalink.

30 Responses to Session Token Support for ASP.NET Web API

  1. ivarkatmo says:

    Great! Looking forward to the javascript example :-)

  2. vivaladan says:

    Oh wow this is great to read so thanks. The Javascript example would be nice, but so too would an elaboration on how this might be combined with “standard” authentication as you put it.

    • I will add the JS sample soon.

      With combined I mean – it is up to you if you want to use the session token. You can also send the original credentials on every request.

  3. What about using cookies for sessions even for desktop clients?

  4. I wanted an explicit approach. Whereas cookies are more implicit. Also cookies have limited metadata.

    The SAM would create a hosting dependency, and compared to the WIF session cookie, JWT is a really compact format.

  5. Pingback: SessionToken and WebAPI Authorization now in Thinktecture.IdentityModel.40 « brockallen

  6. Tim says:

    Trying to figure out if SPA apps built with js mvc libraries could utilize this. Great job!

  7. SDatta says:

    Is the session token and expiration persisted somewhere on the server for subsequent REST service calls authentication ?

  8. Sebastian says:

    Hi Domonick,
    can I get the Token and the Expiration out from a Controller? I want my self give it back with some Systemsettings.

  9. Maybe you have to explain what exactly you are trying to do. I will answer next week (holidays)

    • Sebastian says:

      I want a Login Action on my WebApi Server which send the Token and some Userdata back. The authorization header is at this point Basic, but the authorization is through, but I find noch Point where I can get the token or make a new token.

  10. Josh says:

    Hi Dominick,
    I have two questions…
    1. How would I add to (or replace) the default claims that get added to the token when it’s first issued?
    2. I have a scenario where I’d like to dynamically add claims as the user calls different parts of my Web API (so I don’t have to keep hitting the db for authorization checks if I’ve already added a claim for a particular item). So would it be possible to store the claims in cache on the server and use the token only as a reference to them (similar to SessionAuthenticationModule.IsReferenceMode)?

    • Hi,

      1) On the ClaimsAuthenticationManager property you can set the logic that handles what goes into the token.
      2) I wouldn’t do that (issuing new tokens again and again). Store identity information in the token and cache whatever saves you from the db roundtrips.

  11. NickS says:

    Hi Dominick,

    I’m not sure what I’m missing in order to obtain a session token. I have two projects, one a WebAPI project, the other an MVC 4 project. I’ve configured the WebAPI project to use the ActiveDirectoryMembershipProvider MembershipProvider, installed the Thinktecture.IdentityModel nuget package, and added the following to the bottom of WebApiConfig’s Register() method:


    var authConfig = new AuthenticationConfiguration
    {
    RequireSsl = false, // Just for testing!
    DefaultAuthenticationScheme = "Basic",
    EnableSessionToken = true
    };

    // setup authentication against membership
    authConfig.AddBasicAuthentication((userName, password) => Membership.ValidateUser(userName, password));

    config.MessageHandlers.Add(new AuthenticationHandler(authConfig));

    I have decorated one of my ApiControllers with the [Authorize] attribute.

    I can succesfully authenticate and retrieve JSON data from WebAPI using HttpClient() in the MVC 4 project.

    However, no token is returned after authenticating as far as I can tell (I’ve confirmed this with Fiddler).

    Am I misunderstanding, or should a token be sent back when the client authenticates? I see in your example code above that the HttpClient call is to a URI called “token”:

    var response = client.GetAsync(“token”).Result;

    What further configuration is necessary to retrieve a token?

    Thank you!

  12. nickscave says:

    Thanks Dominick. Ben’s blog post was very helpful. I finally realized that 1)one needed to access a token endpoint specifically, rather than getting a token back any time one authenticated against a WebAPI method, and 2)the token endpoint (“/api/token” with my routing configuration) was built-in; I had been trying to access it at /token, getting a 404, and wondering if I needed to implement an endpoint of my own. It was definitely a head slapping moment when I tried to access /api/token instead of /token, and received a session token back!

    I also found Ben’s notes on his implementation helpful, particularly that one should make sure that the forms authentication cookie times out prior to the token timeout, and also to configure a global action filter to handle the scenario when the application recycles or restarts, and the authentication cookie is still good, but the session has been cleared.

    Thanks again for your hard work on this project, and for your helpful blog posts such as the one above.

  13. SDatta says:

    Hi Dominick,

    My webapi basic auth and session token were working well on IISExpress and IIS. We decided to migrate to Windows Azure and the token endpoint is not present anymore. I created a new azure project with asp.net mvc 4 and webapi and the latest thinktecture nuget package. The error I am getting is
    {“Message”:”No HTTP resource was found that matches the request URI ‘https://xxx/api/v1/token’.”,”MessageDetail”:”No type was found that matches the controller named ‘token’.”}
    I have tried referencing the latest dll from git code after removing the nuget and the result is same,
    The basic authorization works fine, its just the token endpoint is missing.
    I checked that there are no open issues regarding this on git but I am wondering if you are aware of any such issue and possible solution.

    Thank you.

  14. SDatta says:

    Never mind. I changed my api maproute in the new site and got tripped by that.
    My map route
    config.Routes.MapHttpRoute(
    name: “DefaultApi”,
    routeTemplate: String.Format(“{0}/v1/{{controller}}/{{action}}/{{id}}”, “api”),
    defaults: new { id = RouteParameter.Optional, action = RouteParameter.Optional }
    );
    I was calling –

    https://xxx/api/v1/token

    I needed to call

    https://xxx/api/v1/myshinycontroller/token

    Where myshinycontroller has the [Authorize] attribute.

    Thank you again!

  15. nickscave says:

    Dominick: Is this session token support compatible with a web farm? That is, if the token endpoint is deployed on multiple servers, any one of which may answer a request, will the issued token be valid across all servers? If it matters, the WebAPI app that contains the endpoint is configured to use out of process, SQL Server based session state (using Microsoft ASP.NET Universal Providers).

    • nickscave says:

      Naturally, a few minutes after posting my question above, I see by re-reading the very same blog post that you referred me to on Feb 10 that you can specify a SigningKey for use in a server farm scenario:

      var authConfig = new AuthenticationConfiguration
      {
      DefaultAuthenticationScheme = "Basic",
      SessionToken = new SessionTokenConfiguration
      {
      SigningKey = Constants.SessionTokenSigningKey
      }
      EnableSessionToken = true,
      SendWwwAuthenticateResponseHeader = true,
      RequireSsl = false // Just for testing!
      };

      Now I just need to figure out what goes into SigningKey; it’s of type string. How would one generate a signing key to place in a constant like SessionTokenSigningKey above?

      • nickscave says:

        I truly wish I could edit my original comment(s), but I will hang my head in shame and reply to my reply, for the potential betterment of all. After some searching and combing through the IdentityModel source code, I found that the default value of SessionTokenConfiguration.SigningKey is “Convert.ToBase64String(CryptoRandom.CreateRandomKey(32));”. I hope that may save someone else a few minutes. I will now slink quietly away.

  16. Pingback: Sunil Ravulapalli | Enable Basic Authetication in Asp.net web.api using Thinktecture.IdentityModel

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s