Web API Security: Basic Authentication with Thinktecture.IdentityModel AuthenticationHandler

In my last post, I showed how to configure the AuthenticationHandler using the AddMapping method. While you have full control here, I added a number of convenience extension methods that cover common use case. Following is an overview of the HTTP Basic Authentication related extensions.

Simple
The following code is the simplest way to setup Basic Authentication:

  • Credential is expected on the Authorization header using a scheme of Basic
  • Validation is done by the default membership provider
  • Www-Authenticate header with scheme of Basic and a realm of localhost get sent back with the 401
var config = new AuthenticationConfiguration
{
    RequireSsl = true
};
            
// delegate validation to membership provider
config.AddBasicAuthentication(Membership.ValidateUser);

 

In addition you can pull roles from the default role provider like this (or any other method with the same signature):

// delegate validation to membership and fetch roles from role manager
config.AddBasicAuthentication(
    Membership.ValidateUser, 
    Roles.GetRolesForUser);

 

Custom credential validation
You can use whatever logic you like to validate the credentials. As long as your method takes two strings (user name and password) and returns a bool (success or not).

// custom username validator
config.AddBasicAuthentication(ValidateUser);

 

When credential validation fails, a 401 status code gets sent back. You can control the exact layout of the response by throwing an AuthenticationException inside the credential validation logic. The following code shows how to send back a 400 (bad request) instead of a 401 (sample code!):

private static bool ValidateUser(string userName, string password)
{
    if (userName == "bob")
    {
        throw new AuthenticationException
        {
            StatusCode = HttpStatusCode.BadRequest,
            ReasonPhrase = "Invalid client"
        };
    }
 
    return true;

}

Retain password
Sometimes you want to defer validation of credentials, or need the client password for delegation. In that case you can use the retain password option to save the password as a password claim that you can access later from the claims collection:

// retain password
config.AddBasicAuthentication(ValidateUser, retainPassword: true);
 

Custom realm and headers
You can also specify a realm for the response header:

// different realm

config.AddBasicAuthentication(ValidateUser, “MyRealm”);

…and to look for credentials on a different header:

// different header
config.AddBasicAuthentication(
    ValidateUser, 
    AuthenticationOptions.ForHeader("X-Authorization"), 

    “My Realm”);

 

Claims Transformation
If you need more control over the returned claims, you can specify a ClaimsAuthenticationManager derived implementation on AuthenticationConfiguration.

You can of course also create new extension methods over the raw AddMapping functionality if something you frequently need is missing.

HTH

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

58 Responses to Web API Security: Basic Authentication with Thinktecture.IdentityModel AuthenticationHandler

  1. jholovacs says:

    Where can I find an example of this code? I can’t figure out where stuff goes.

  2. Hi Dominick,

    I’ve been doing browser based security for a long time and I’m new to this whole concept of RESTful APIs (used to RPC). I’ve been doing a lot of reading and research because I’m building a MVC4 web app that also has Web APIs in the same project. (using forms authentication for the MVC4 part). The APIs are being called by native smartphone apps (Android/iOS) and I’m looking to use the Basic Authentication but once authenticated, I would like the request/responses to use token based authentication (don’t want to pass the login/password back and forth all the time even though the connection will be SSL enabled). I think that is the correct way from what I’ve read.

    So with that being said, I want to say what you’ve done is looks pretty amazing and I’m trying to piece it all together using your samples but I want to make sure I’m using the right one. So for what I’ve described, I believe I should be looking at your Web API Security sample or is the Forms and Basic Auth sample better?

    Thanks in advance for the help,
    -Chris

  3. 2nouillesahanoi says:

    Hi Dominick !
    I try to authenticated a user in Basic mode (Form auth) on my web Api from an external client (html ajax) with Thinktecture identityModel !!
    I look the sample (git projects), I read many post of you or BrockAllen but I can’t do it.
    I think that you can help me very easily, I’m not far from the goal but…
    If you can help me, where can i show you a little code ? here ? email ?

    Thank’s a lot

    Thomas

  4. tomadj2 says:

    Ok ! Thank’s Dominick !
    So, my goal is :
    I have a web API, with a controllerAPI with 1 action with an Authorize attribute.
    From an external client, I call my Authorize action with ajax. The first time, I catch the status code 401 Unauthorized and I open a popup (field login,field password and button login) for log user.
    the login button launch a ajax request to my controllerApi “Account”. But the second time, when i would like access to the autorise action, again request return 401.

    (Of course, i use Thinktecture identityModel, last nuget package)

    The important code on gist :


    [InitializeSimpleMembership]
    public class AccountController : ApiController
    {
    public object LogUser(MyLoginModel user)
    {
    if (ModelState.IsValid && Membership.ValidateUser(user.Login, user.Password))
    {
    // set authentication cookie
    FormsAuthentication.SetAuthCookie(user.Login, true);
    return new { success = true };
    }
    else
    {
    return new { success = false, message = "Le nom d'utilisateur ou mot de passe fourni est incorrect." };
    }
    }
    }


    //ajax request to log user accountControllerAPI
    $.ajax({
    type: "POST",
    url: urlApi+"Account/LogUser",
    data: self.userLog,
    error: function (msg) {
    console.log(msg);
    alert("Error !: " + msg);
    },
    success: function (data) {
    console.log(data)
    }
    });
    //ajax request to Authorize action
    $.ajax({
    type: "POST",
    url: urlApi+"Rank",
    dataType: "json",
    data:{IdMatch:4},
    error: function (msg) {
    console.log("retour error");
    console.log(msg);
    if(msg.status == '401')
    {
    $("#formAuth").dialog({
    width:1000,
    title:'Login !'
    });
    }
    },
    success: function (p) {
    //affiche le contenu du fichier dans le conteneur dédié
    console.log("callbacl success post");
    console.log(p);
    }
    });


    protected void Application_Start()
    {
    AreaRegistration.RegisterAllAreas();
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RankOl.Common.Security.WebApiConfig.Register(GlobalConfiguration.Configuration);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
    AuthConfig.RegisterAuth();
    }
    // claims transformation
    protected void Application_PostAuthenticateRequest()
    {
    if (ClaimsPrincipal.Current.Identity.IsAuthenticated)
    {
    var principal = new ClaimsTransformer().Authenticate(string.Empty, ClaimsPrincipal.Current);
    HttpContext.Current.User = principal;
    Thread.CurrentPrincipal = principal;
    }
    }

    view raw

    global.asax

    hosted with ❤ by GitHub


    public static class WebApiConfig
    {
    public static void Register(HttpConfiguration config)
    {
    CorsConfiguration corsConfig = new CorsConfiguration();
    corsConfig.AllowAll();
    var corsHandler = new CorsMessageHandler(corsConfig, config);
    config.MessageHandlers.Add(corsHandler);
    // authentication configuration for identity controller
    var authentication = CreateAuthenticationConfiguration();
    config.MessageHandlers.Add(new AuthenticationHandler(authentication));
    // default API route
    config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
    );
    }
    private static AuthenticationConfiguration CreateAuthenticationConfiguration()
    {
    var authentication = new AuthenticationConfiguration
    {
    ClaimsAuthenticationManager = new ClaimsTransformer(),
    SendWwwAuthenticateResponseHeaders = true,
    InheritHostClientIdentity = true,
    EnableSessionToken = false,
    RequireSsl = false
    };
    #region Basic Authentication
    authentication.AddBasicAuthentication((username, password)
    => Membership.ValidateUser(username, password));//UserCredentials.Validate(username, password));
    #endregion
    return authentication;
    }
    }

    view raw

    WebApiConfig.cs

    hosted with ❤ by GitHub

    Thank you very mutch for the time you can give me.
    I am at your disposal for more code or anythings

    Thomas

  5. tomadj2 says:

    Both ??,I use the both ? Sorry but I certainly misunderstood. I thought not that I use both.
    I use the form, the first time if the user is not authenticated, in order to the user can, after, access to the Authorise Actions
    what should I do in my case then ?

  6. tomadj2 says:

    (I call my web API from an html page in a wordpress blog, on a different server)

  7. On one hand you set a Forms Auth cookie – on the other you add Basic Authentication to the auth config.

    Does the browser round trip your forms auth cookie after the login? That should be sufficient.

  8. tomadj2 says:

    What should I do then to the Basic Authentication works ?
    I remove FormsAuthentication.SetAuthCookie(user.Login, true); in account action ?? :

    if (ModelState.IsValid && Membership.ValidateUser(user.Login, user.Password))
    {
    //FormsAuthentication.SetAuthCookie(user.Login, true);
    return new { success = true };
    }
    but I can’t access to my authorize action of api controller.

    I forget what ? why never enters in the if :

    protected void Application_PostAuthenticateRequest()
    {
    if (ClaimsPrincipal.Current.Identity.IsAuthenticated)
    {
    var principal = new ClaimsTransformer().Authenticate(string.Empty, ClaimsPrincipal.Current);
    HttpContext.Current.User = principal;
    Thread.CurrentPrincipal = principal;
    } }
    In other words, what to do for that after login, ajax request to an Authorze action don’t return 401 ?
    you saw my ajax requests to LogUser and Post ( [Authorize] ), but I should add other things ? in header request ? or somewhere else ?

    sorry, I’m bad !
    Thank’s again for your help.

  9. So the question would be –

    – Is the forms auth cookie being set when you call the login method?
    – Is it round-tripped from that point on?

  10. tomadj2 says:

    Yes, I’m a little lost between auth basic auth and form and what for me thinktecture.
    So, to answer your questions :

    1) I think not, because I remove FormsAuthentication.SetAuthCookie(user.Login, true);
    I thought you told me that it’s for form based auth.

    2) For round-trip, I don’t understand. :(

    So, already, is that we agree on the method? me I have to, in my case (webapi), the basic auth and not form based auth ?

    I read again your post https://leastprivilege.com/2012/10/23/mixing-mvc-forms-authentication-and-web-api-basic-authentication/

    effectively, in the sample, in the client , there are :
    client.DefaultRequestHeaders.Authorization =
    new BasicAuthenticationHeaderValue(“alice”, “alice”);

    Me, I must do the equivalent in my ajax request to the authorize action ?? .

    and also, for the time being, my action LogUser return just true if the user is validate. it is at this level that I need to do something else ? (in reference to the round trip maybe ?)

  11. Using forms authentication might be totally enough here, if the cookie is successfully set (and also sent by the Ajax calls), we don’t need Basic Authentication. But you need to verify that.

  12. tomadj2 says:

    Ok thank’s ! I can’t test now at work but this evening I will try to remove basic auth and just keep form auth.

    Server side :
    – In the LogUser Action, if the user is validate I set the AuthCookie with : FormsAuthentication.SetAuthCookie(user.Login, true);

    I remove just the lines : authentication.AddBasicAuthentication((username, password)
    => Membership.ValidateUser(username, password)); in AuthenticationConfiguration ?

    And client side, must be added in the header of the ajax request to the [Autorize] action the cookie. How can I do that ?

  13. Again – you need to make sure the client sends the cookie after calling login. Then there is nothing else you need to do.

    Please read up on cookies and how forms authentication is supposed to work. Otherwise I can’t help you.

  14. tomadj2 says:

    Ok thank’s Dominick. I will search and learn more on forms authentification.

  15. Pingback: Web API Security: JSON Web Token/OAuth2 with Thinktecture.IdentityModel AuthenticationHandler | www.leastprivilege.com

  16. tomadj2 says:

    Hi Domick !
    I understood my problem !! But I don’t know solve it.

    Consider just two event button click and two controller action (a little bit of code ) :


    <script>
    $(document).ready(function () {
    $("#btnLogUser").click(function () {
    $.ajax({
    type: "POST",
    url: 'api/test/',
    error: function (msg) {
    alert("Error !: " + msg);
    },
    success: function (data) {
    console.log(data);
    }
    });
    });
    $("#AuthrizeAction").click(function () {
    $.ajax({
    type: "GET",
    url: 'api/test/',
    error: function (msg) {
    alert("Error !: " + msg);
    },
    success: function (data) {
    console.log(data);
    }
    });
    });
    });
    </script>
    <button id="AuthrizeAction">is Authorize ?</button>
    <button id="btnLogUser">log</button>

    view raw

    client.html

    hosted with ❤ by GitHub


    // POST api/test
    //Action log user
    public object Post()
    {
    var success = Membership.ValidateUser("tomadj", "thomas");
    if (success)
    {
    FormsAuthentication.SetAuthCookie("tomadj", true);
    return new { success = true, redirect = "" };
    }
    else
    {
    return new { success = false, redirect = "" };
    }
    }
    // GET api/test
    //Authorize action
    [Authorize]
    public IEnumerable<string> Get()
    {
    return new string[] { "value1", "value2" };
    }

    IF the code client is in the same server ( IIs localhost visualstudio 2012 project MVC 4 + webAPI ) run from on a razor page html –> http://localhost:5314/home/index :

    1 ) click on “is Authorize ?” button –> return 401 Unauthorized
    2 click on “login” button –> return success: true
    3) click again on is Authorize ? button –> return OK user is authentificated

    however if I call my web api from an other server/site (in my case, a wordpress blog in a html page of this blog ) :

    1 ) click on “is Authorize ?” button –> return 401 Unauthorized
    2 click on “login” button –> return success: true
    3) click again on is Authorize ? button –> RETURN 401 UNAUTHORIZED

    I guess :
    1- I must get the “context/cookie” in the callback of action login if return true.
    2- I must set the “context/cookie” before call again the authorize action

    No ?
    if is the good solution, can you help me for do that ?

    Thank’s again !

    Thomas

  17. tomadj2 says:

    it’s what alows the browser to send the cookie over ajax

  18. Karel Vandenhove says:

    Hi,
    This must be a stupid question, but where can I find the /webapisecurity/api/ controller for the Web Api Security Project?
    I’ve got the FormsAndBasicAuth running, but cannot get to the /webapisecurity/api/

    Thanks!

    Karel

  19. Pingback: ASP.NET Web API Authentication: Using multiple (simultaneous) Authentication Methods with Thinktecture AuthenticationHandler | www.leastprivilege.com

  20. jholovacs says:

    Hi Dominick,
    This is a great library. I had this working swimmingly in my test environment (no ssl required) however when I published to my hosting server (arvixe.com) and I set the RequireSsl (I don’t know if it’s related to my issue) I get a 401.1 error every time, and I cannot duplicate this in my test environment. I was wondering what thoughts you might have on what could be the problem.

    • Hey,

      no sorry – do you have access to event logs etc?

      • jholovacs says:

        Unfortunately not. If I find out what the problem is, I will mention it here.

    • jholovacs says:

      OK, for anyone else who might have this problem: it is important that IIS NOT be set to allow basic authentication if you are using this library’s basic authentication. A bit counter-intuitive, but unchecking that box made a world of difference.

  21. phillee007 says:

    Hi there,
    Does this work with the latest release of Asp.Net and VS 2013 Preview? I’ve tried following the steps you mention using the vanilla MVC app template, but when I do an api call always end up getting the 302 redirect to the login page, rather than getting a 401. I’m wondering if this is because the authentication middleware is rewriting the 401 response (created by Thinktecture.IdentityModel) to a 302?

    Just to confirm, I:
    – Created a new Asp.Net Web Application project in VC 2013 Preview, selecting the MVC project type, and checking the Web Api box
    – Added the Thinktecture.IdentityModel Nuget package
    – Added the code to my WebApiConfig.cs file as follows:
    var authConfig = new AuthenticationConfiguration
    {
    RequireSsl = false
    };

    // setup authentication against membership
    authConfig.AddBasicAuthentication((userName, password) => IdentityConfig.Secrets.Validate(userName, password).Result);
    config.MessageHandlers.Add(new AuthenticationHandler(authConfig));

    – Placed the [AuthorizeAttribute] on the ValuesController
    – Debugged the app and got the redirect

    When debugging, I simply went to the homepage and did an ajax request to the api in the browser as follows:
    $.get(‘/api/values’, function(data) {});

    but rather than seeing the 401 response with the www-authenticate header, I got a 302 response instead of a 401 (NOTE: the 302 actually contained the www-authenticate header)

    Any ideas?

    Cheers,
    Phil

    • In general this all works with VS2013 (or rather web api v2) – but the new templates use the OWIN middleware infrastructure and I wouldn’t be surprised if they interfere with our plumbing. That said – all the OWIN stuff is optional.

      I haven’t tried it yet.

      • Phil Lee says:

        Thanks Dominick. Interestingly, when I create a new internet application and just select Web Api (not MVC) the basic auth works as expected. However, with the MVC project, I think it must be the app.UseApplicationSignInCookie(); call in Startup.Auth.cs which causes OWIN to intercept the 401. My reason for thinking that is the flow outlined in http://blogs.msdn.com/b/webdev/archive/2013/07/03/understanding-owin-forms-authentication-in-mvc-5.aspx.

        Sadly this makes it a bit tricky as I was hoping to use a combination of forms auth for the website and basic auth (or JWT-based) for the api. Might have to see if I can just use JWT for the whole thing and use js for the login etc for the website…

      • And why do you want to use OWIN? I’d wait until the bits are more stabilized.

      • Phil Lee says:

        It comes with the default project template so I figured it was worth having a go at, in anticipation of it becoming more mainstream in the near future. I also wanted to use the attribute routing in Web Api 2, hence my desire to use the VS2013 template… Might have to try resorting to the VS2012 MVC template for now, although I’d love to have attribute routing in there.

      • OWIN and attribute routing are unrelated.

  22. psharma says:

    Hi Dominick,
    I have implemented ThinkTecture Basic Authentication in my Web API Project. I am using the [Authorize] attribute to secure my controllers. I am passing UserName and Password in the Request Headers via client application. The API is able to authorize the user correctly and the RolePrincipal.Current.Identity.Name; call return the UserName passed via Request Header from the client application as well as Chrome Advanced Rest Client. However I am not able to authorize when calling the API from the API Unit test project. The RolePrincipal.Current.Identity.Name; returns my Windows logon instead of the UserName passed via Request Headers. I am using the following code in the Test Project to send my User/Password. This is occurring on my local development machine. Can you please advice why this might be happening?

    var username = ;
    var password = “;
    var config = new HttpConfiguration();
    var request = new HttpRequestMessage(httpMethod, “http://localhost/api/person”);
    var route = config.Routes.MapHttpRoute(“DefaultApi”, “api/{controller}/{id}”);
    var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { “controller”, “person” } });
    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(“application/json”));
    request.Headers.Authorization = new AuthenticationHeaderValue(“Basic”, EncodeToBase64(string.Format(“{0}:{1}”, username, password)));
    controller.ControllerContext = new HttpControllerContext(config, routeData, request);
    controller.Request = request;
    controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;

    • Well – as far as i can tell you are not wiring the AuthenticationHandler in in your test. You need to add it to the message handler collection.

      • psharma says:

        Hi Dominick,
        Thanks for your prompt response. I modified the code based on your suggestion, however RolePrincipal.Current.Identity.Name; in the Web API still returns my windows login instead of the username passed. Am I missing something. Do you have sample for this?

        var username = ;
        var password = ;
        Uri _address = new Uri(“http://localhost/api/person”);
        var config = new HttpConfiguration();
        var request = new HttpRequestMessage(httpMethod, “http://localhost/api/person”);
        var route = config.Routes.MapHttpRoute(“DefaultApi”, “api/{controller}/{id}”);
        var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { “controller”, “person” } });
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(“application/json”));
        request.Headers.Authorization = new AuthenticationHeaderValue(“Basic”, EncodeToBase64(string.Format(“{0}:{1}”, username, password)));

        var authConfig = new AuthenticationConfiguration();
        // setup authentication against membership
        authConfig.AddBasicAuthentication((userName, Password ) => Membership.ValidateUser(username, password));
        config.MessageHandlers.Add(new AuthenticationHandler(authConfig));

        controller.ControllerContext = new HttpControllerContext(config, routeData, request);
        controller.Request = request;
        controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;

  23. Try Controller.User instead.

  24. akhileshgandhi says:

    I am doing this
    authentication.AddBasicAuthentication(SessionDataContextCache.Validate,retainPassword: true);
    How is it possible to retrieve password from claims as I want to pass password in another service

  25. Phil Lee says:

    Hi Dominick,

    Is it possible to configure the lifetime of the auth token issued by the /token endpoint? Also, is it possible to do this without affecting the lifetime of the regular auth cookie? I’m trying to either make give the token a very far expiration date so it’s effectively persistent, or at least give it a lifetime of 15 days or more. I couldn’t see an option in the AuthenticationConfiguration to allow this.

    Thanks,
    Phil

  26. Kenny Kaplan says:

    Dominick,
    I’ve got the code working correctly within Visual Studio, but when I export it to IIS the authentication doesn’t work. There is a popup asking me for credentials but they aren’t being validated by your methods. It seems as though they are being validated first against Windows users. I have added logging and the API never gets to the SendAsync method. Within IIS I have Anonymous Authentication as the only enabled item. Is there something I’m missing with the configuration that will get the user name and password down to the security config and handled within Web API?
    Thanks,
    Kenny

    • Kenny Kaplan says:

      Never mind. I think the problem I was having related to referencing Mapped Drives and not to anything specific to the Web API security.

  27. Been using this for quite a while.
    Started a new webapi project and noticed(because it’s the first time i wanted a call unauthenticated) that the [AllowAnonymous] attribute was being ignored(authentication was always required.
    Does anyone know how to still keep using AuthenticationConfiguration and make allowanonymous attribute work?
    Can they even be checked before this authentication happens?

  28. Alex John K says:

    Is it possible for Http Basic Authentication to validate user from Identity Server database?

  29. Naeem Arshad says:

    Is it working with identity Server 3 ? If yes , is there any example?

  30. stevegtr says:

    If you use the additional AuthenticationOptions.ForHeader(“MyAuthorization”) parameter your encoded header value (named MyAuthorization) should not be prefixed with “Basic ” or the Thinktecture.IdentityModel.WebApi.AuthenticationHandler code will throw a base-64 encoding exception. To your clients it will appear as a 401 unauthorized error. Is this by design?

Leave a reply to Dominick Baier Cancel reply