Thinktecture.IdentityModel and ASP.NET Web API

As part of my work with the new Thinktecture.IdentityModel and JWT, I also updated the ASP.NET Web API integration. My first drop was based on this article. But had some limitation.

The next version is easier to use and much more flexible. It come out-of-the-box with support for:

  • Basic Authentication
  • Simple Web Tokens
  • JSON Web Tokens
  • Access Keys
  • SAML 1.1 & 2.0

The first version only supported the authorization header, now I am able to retrieve credentials from various locations like:

  • the authorization header (scheme / credential)
  • some other header
  • query string parameter
  • client certificate
  • cookie

The usage is dead simple, you first setup a configuration that describes the credentials you want to support and where to look for them – then you let my library do the hard work to turn those credentials into a claims principal. Some examples:

JSON Web Token on the authorization header with a Bearer scheme

config.AddJsonWebToken(

    issuer: http://issuer.com”,

    audience: http://rp.com”,

    signingKey: “123”,

    options: AuthenticationOptions.ForAuthorizationHeader(“Bearer”));

 

Some access key on a query string called apikey

config.AddAccessKey(

    handler: apiKeyHandler,

    options: AuthenticationOptions.ForQueryString(“apikey”));

 

Basic Authentication using an ASP.NET Membership provider

config.AddBasicAuthentication((username, password) =>

    Membership.ValidateUser(username, password));

 

After you’ve built the configuration, you can run the authentication anywhere you want, e.g. in a global/per-route message handler, a filter or even on the controller/action method itself. You simply pass in the current HttpRequestMessage:

var authN = new HttpAuthentication(config);

ClaimsPrincipal principal = authN.Authenticate(request);

 

The Authenticate method has a well defined behavior:

  • returns an authenticated ClaimsPrincipal if a configured credential was found and successfully authenticated.
  • returns an anonymous ClaimsPrincipal if no configured credential was found
  • throws a SecurityTokenValidationException if a credential was found, but authentication failed.

Not sure I can make that any simpler ;)

Once I’ve run more tests, I will upload the code to github.

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

15 Responses to Thinktecture.IdentityModel and ASP.NET Web API

  1. I would love to check this out!

  2. tabishsarwar says:

    I looked at the sample provided and implemented Basic Authentication . It works when i am testing local machine . When deployed to IIS .Authentictaion works with a .Net Http Client . However from jquery or using POSTMan . It seems the authenctication is not working and call to api works. What is that i am missing . Do i need to change anything at IIS as well ?

  3. tabishsarwar says:

    I looked at the samples provided and implemented Basic Authentication as a starting point. Authentication works locally . But when deployed on IIS. It works with .Net Http client . Any direct call from jquery or rest client like postman works without authentication . Is there any setting or changes that i need to make in IIS ?

  4. tabishsarwar says:

    I figured out that [Authorize] attribute should be on Controller to make it work. But why do i have to put that attribute . Cant it be taken care on HTTP Handler side without using the attribute thing ? Let me know if i am missing something .

    • The authentication handler’s job is to turn a credential (if present) into a principal. The [Authorize] attribute determines if authentication is actually needed for the resource.

      These are two independent things.

  5. dhilgarth says:

    Thanks for all the info here.
    I have a general design question when using claims based identity with the ASP.NET Web API:
    In many controller actions, I need the internal User-ID of the authenticated user to filter the data that is returned from the action (e.g. GET http://host/api/mytasks).
    How would you go about implementing this?
    Is see at least three possibilities:
    1. Use the name claim and look up the corresponding ID inside the action itself, like: int id = userRepository.ByUsername(ClaimsPrincipal.Current.Identity.Name).Id;
    2. Use a custom claim type, like: int id = int.Parse(((ClaimsIdentity)ClaimsPrincipal.Current.Identity).FindFirst(CustomClaimTypes.UserId));
    The problem here: How to intercept thinktecture.IdentityModel to add that custom claim and when should that happen?
    3. Use a custom identity with a property for the User-ID, like: int id = ((CustomIdentity)ClaimsPrincipal.Current.Identity).UserId;
    Again: How and when to do this? And is this even a good idea?

    How would you solve this?
    Thanks for any insights!

    Daniel

    • If you need the id frequently, I would add it as a claim.

      A claims authentication manager would the right place to do that. See the corresponding property on the AuthenticationConfiguration class.

      • dhilgarth says:

        I see, similar to the ConsultantsClaimsTransformer in the WebApiSecurity solution?

        Can you elaborate on why you would add a new claim that way? Why not create a ConsultantsClaimIdentity with a real property for each claim? I am asking because with the generic ClaimsIdentity and its string based claims collection, I would need to parse all the information to their corresponding types in each controller action. In this example, it is only the ID, but it could be more, like a date for example.

      • Well – this is just syntactic sugar. Of course you can provide your own principal (or an extension method for ClaimsPrincipal). But the underlying storage mechanism should be the claims collection.

      • Daniel Hilgarth says:

        Thanks for your answer. Why do you say it should still be based on the claims collection? That would mean there still is the conversion to and from string. And why are you saying it should be my own ClaimPrinciple and not a ClaimIdentity?

      • The claims collection is the new data storage mechanism for identity data. All .NET APIs are based on that. So you should use it like that. What’s the problem with string conversion?

        ClaimsPrincipal should be “main” class to work with. That’s what you get when calling ClaimsPrincipal.Current (which should be the API to use for grabbing the current user). I personally would add an extension method for it to retrieve your ID.

        You can of course implement it any way you like – but that’s the way it is supposed to be.

      • If the implementation is getting a user object when validating the credentials, would it be possible to pass any properties along for them to be used when creating custom claims in the transformer? I’d like to avoid having to make another call to get user details after I already made one call when validating the user.

      • I am sure you can – but I stopped working on this for quite while. Most of the features are now built-in Web API v2.

  6. joezen777 says:

    FYI: If you’re getting errors, make sure you recycle your app pools after each configuration or build change.

  7. Damian says:

    Hi Dominic,

    I have a design question on this and need your help! I have an ios app, which users can authenticate natively, via Facebook and Twitter. My conceptual design for this is as follows for each authentication flow:

    1) User logs in with native username and password = oauth2 token issued by my web api service.
    2) User logs in with Facebook = Service checks Facebook token directly with facebook, validates users and then issues oauth2 token via web api service.
    3) User logs in with Twitter= Service checks Twitter token directly with Twitter, validates users and then issues oauth2 token via web api service.

    In every subsequent request to the service I will validate my web api token thus avoiding validating the facebook/twitter token on every call with facebook/twitter. In the last two cases my assumption is that the token issued will have the same lifetime as the twitter/facebook tokens.

    Can you validate/recommend good practice here and where identity server and or authorization server fits in?

    Kind regards,

    Damian

Leave a comment