Zulfiqar's weblog

Middleware, security & random .Net

Claim-based-security for ASP.NET Web APIs using DotNetOpenAuth

Posted by zamd on May 4, 2012


Source Code

Recently I worked with a customer assisting them in implementing their Web APIs using the new ASP.NET Web API framework. Their API would be public so obviously security came up as the key concern to address. Claims-Based-Security is widely used in SOAP/WS-* world and we have rich APIs available in .NET Framework in the form of WCF, WIF & ADFS 2.0. Even though we now have this cool library to develop Web APIs, the claims-based-security story for REST/HTTP is still catching up. OAuth 2.0 is almost ready, OpenID Connect is catching up quickly however it would still take sometime before we have WIF equivalent libraries for implementing claims-based-security in REST/HTTP world. DotNetOpenAuth seems to be the most prominent open-source library claiming to support OAuth 2.0 so I decided to give it a go to implement the ‘Resource Owner Password Credentials’ authorization grant. Following diagram shows the solution structure for my target scenario.

clip_image002

1. OAuth 2.0 issuer is an ASP.NET MVC application responsible for issuing token based on OAuth 2.0 ‘Password Credentials’ grant type.

2. Web API Host exposes secured Web APIs which can only be accessed by presenting a valid token issued by the trusted issuer

3. Sample thick client which consumes the Web API

I have used the DotNetOpenAuth.Ultimate NuGet package which is just a single assembly implementing quite a few security protocols. From OAuth 2.0 perspective, AuthorizationServer is the main class responsible for processing the token issuance request, producing and returning a token for valid & authenticated request. The token issuance action of my OAuthIssuerController looks like this:

OAuth 2.0 Issuer

public class OAuthIssuerController : Controller {
    public ActionResult Index()
    {
        var configuration = new IssuerConfiguration {
            EncryptionCertificate = new X509Certificate2(Server.MapPath("~/Certs/localhost.cer")),
            SigningCertificate = new X509Certificate2(Server.MapPath("~/Certs/localhost.pfx"), "a")
        };

        var authorizationServer = new AuthorizationServer(new OAuth2Issuer(configuration));
        var response = authorizationServer.HandleTokenRequest(Request).AsActionResult();

        return response;
    }
}

AuthorizationServer handles all the protocol details and delegate the real token issuance logic to a custom token issuer handler (OAuth2Issuer in following snippet)

Protocol independent issuer
  1. public class OAuth2Issuer : IAuthorizationServer
  2. {
  3.     private readonly IssuerConfiguration _configuration;
  4.     public OAuth2Issuer(IssuerConfiguration configuration)
  5.     {
  6.         if (configuration == null) throw new ArgumentNullException(“configuration”);
  7.         _configuration = configuration;
  8.     }
  9.     public RSACryptoServiceProvider AccessTokenSigningKey
  10.     {
  11.         get
  12.         {
  13.             return (RSACryptoServiceProvider)_configuration.SigningCertificate.PrivateKey;
  14.         }
  15.     }
  16.     public DotNetOpenAuth.Messaging.Bindings.ICryptoKeyStore CryptoKeyStore
  17.     {
  18.         get { throw new NotImplementedException(); }
  19.     }
  20.     public TimeSpan GetAccessTokenLifetime(DotNetOpenAuth.OAuth2.Messages.IAccessTokenRequest accessTokenRequestMessage)
  21.     {
  22.         return _configuration.TokenLifetime;
  23.     }
  24.     public IClientDescription GetClient(string clientIdentifier)
  25.     {
  26.         const string secretPassword = “test1243″;
  27.         return new ClientDescription(secretPassword, new Uri(http://localhost/”), ClientType.Confidential);
  28.     }
  29.     public RSACryptoServiceProvider GetResourceServerEncryptionKey(DotNetOpenAuth.OAuth2.Messages.IAccessTokenRequest accessTokenRequestMessage)
  30.     {
  31.         return (RSACryptoServiceProvider)_configuration.EncryptionCertificate.PublicKey.Key;
  32.     }
  33.     public bool IsAuthorizationValid(DotNetOpenAuth.OAuth2.ChannelElements.IAuthorizationDescription authorization)
  34.     {
  35.         //claims added to the token
  36.         authorization.Scope.Add(“adminstrator”);
  37.         authorization.Scope.Add(“poweruser”);
  38.         return true;
  39.     }
  40.     public bool IsResourceOwnerCredentialValid(string userName, string password)
  41.     {
  42.         return true;
  43.     }
  44.     public DotNetOpenAuth.Messaging.Bindings.INonceStore VerificationCodeNonceStore
  45.     {
  46.         get
  47.         {
  48.             throw new NotImplementedException();
  49.         }
  50.     }
  51. }

Now with my issuer setup, I can acquire access tokens by POSTing following request to the token issuer endpoint

Client

POST /Issuer HTTP/1.1

Content-Type: application/x-www-form-urlencoded; charset=utf-8

scope=http%3A%2F%2Flocalhost%2F&grant_type=client_credentials&client_id=zamd&client_secret=test1243

 

In response, I get 200 OK with following payload

 

HTTP/1.1 200 OK

Cache-Control: no-cache, no-store, max-age=0, must-revalidate

Pragma: no-cache

Content-Type: application/json; charset=utf-8

Server: Microsoft-IIS/7.5

Content-Length: 685

{“access_token”:”gAAAAC5KksmbH0FyG5snks_xOcROnIcPldpgksi5b8Egk7DmrRhbswiEYCX7RLdb2l0siW8ZWyqTqxOFxBCjthjTfAHrE8owe3hPxur7Wmn2LZciTYfTlKQZW6ujlhEv6N4V1HL4Md5hdtwy51_7RMzGG6MvvNbEU8_3GauIgaF7JcbQJAEAAIAAAABR4tbwLFF57frAdPyZsIeA6ljo_Y01u-2p5KTfJ2xa6ZhtEpzmC46Omcvps9MbFWgyz6536_77jx9nE3sePTSeyB5zyLznkGDKhjfWwx3KjbYnxCVCV-n2pqKtry0l8nkMj4MrjqoTXpvd_P0c_VGfVXCsVt7BYOO68QbD-m7Yz9rHIZn-CQ4po0FqS2elDVe9qwu_uATbAmOXlkWsbnFwa6_ZDHcSr2M-WZxHTVFin7vEWO7FxIQStabu_r4_0Mo_xaFlBKp2hl9Podq8ltx7KvhqFS0Xu8oIJGp1t5lQKoaJSRTgU8N8iEyQfCeU5hvynZVeoVPaXfMA-gyYfMGspLybaw7XaBOuFJ20-BZW0sAFGm_0sqNq7CLm7LibWNw”,”token_type”:”bearer”,”expires_in”:”300″,”scope”:”http:\/\/localhost\/ adminstrator poweruser”}

image

DotNetOpenAuth also has a WebServerClient class which can be used to acquire tokens and I have used in my test application instead of crafting raw HTTP requests. Following code snippet generates the same above request/response

Get Access Token
  1. private static IAuthorizationState GetAccessToken()
  2. {
  3.     var authorizationServer = new AuthorizationServerDescription
  4.     {
  5.         TokenEndpoint = new Uri(http://localhost:1960/Issuer”),
  6.         ProtocolVersion = ProtocolVersion.V20
  7.     };
  8.     var client = new WebServerClient(authorizationServer, http://localhost/”);
  9.     client.ClientIdentifier = “zamd”;
  10.     client.ClientSecret = “test1243″;
  11.     var state = client.GetClientAccessToken(new[] { http://localhost/” });
  12.     return state;
  13. }

Ok Now the 2nd part is to use this access token for authentication & authorization when consuming ASP.NET Web APIs.

Web API Client
  1. static void Main(string[] args)
  2. {
  3.     var state = GetAccessToken();
  4.     Console.WriteLine(“Expires = {0}”, state.AccessTokenExpirationUtc);
  5.     Console.WriteLine(“Token = {0}”, state.AccessToken);
  6.     var httpClient = new OAuthHttpClient(state.AccessToken)
  7.     {
  8.         BaseAddress = new Uri(http://localhost:2150/api/values”)
  9.     };
  10.     Console.WriteLine(“Calling web api…”);
  11.     Console.WriteLine();
  12.     var response = httpClient.GetAsync(“”).Result;
  13.     if (response.StatusCode==HttpStatusCode.OK)
  14.         Console.WriteLine(response.Content.ReadAsStringAsync().Result);
  15.     else
  16.         Console.WriteLine(response);
  17.     Console.ReadLine();
  18. }

On line 8, I’m creating an instance of a customized HttpClient passing in the access token. The httpClient would use this access token for all subsequent HTTP requests

OAuth enabled HttpClient
  1. public class OAuthHttpClient : HttpClient
  2. {
  3.     public OAuthHttpClient(string accessToken)
  4.         : base(new OAuthTokenHandler(accessToken))
  5.     {
  6.     }
  7.     class OAuthTokenHandler : MessageProcessingHandler
  8.     {
  9.         string _accessToken;
  10.         public OAuthTokenHandler(string accessToken)
  11.             : base(new HttpClientHandler())
  12.         {
  13.             _accessToken = accessToken;
  14.         }
  15.         protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
  16.         {
  17.             request.Headers.Authorization = new AuthenticationHeaderValue(“Bearer”, _accessToken);
  18.             return request;
  19.         }
  20.         protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, System.Threading.CancellationToken cancellationToken)
  21.         {
  22.             return response;
  23.         }
  24.     }
  25. }

Relying Party (ASP.NET Web APIs)

Finally on the RP side, I have used standard MessageHandler extensibility to extract and validate the ‘access token’. The OAuth2 message handler also extracts the claims from the access token and create a ClaimsPrincipal which is passed on the Web API implementation for authorization decisions.

OAuth2 Message Handler
  1. public class OAuth2Handler : DelegatingHandler
  2. {
  3.     private readonly ResourceServerConfiguration _configuration;
  4.     public OAuth2Handler(ResourceServerConfiguration configuration)
  5.     {
  6.         if (configuration == null) throw new ArgumentNullException(“configuration”);
  7.         _configuration = configuration;
  8.     }
  9.     protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  10.     {
  11.         HttpContextBase httpContext;
  12.         string userName;
  13.         HashSet<string> scope;
  14.         if (!request.TryGetHttpContext(out httpContext))
  15.             throw new InvalidOperationException(“HttpContext must not be null.”);
  16.         var resourceServer = new ResourceServer(new StandardAccessTokenAnalyzer(
  17.                                                     (RSACryptoServiceProvider)_configuration.IssuerSigningCertificate.PublicKey.Key,
  18.                                                     (RSACryptoServiceProvider)_configuration.EncryptionVerificationCertificate.PrivateKey));
  19.         var error = resourceServer.VerifyAccess(httpContext.Request, out userName, out scope);
  20.         if (error != null)
  21.             return Task<HttpResponseMessage>.Factory.StartNew(error.ToHttpResponseMessage);
  22.         var identity = new ClaimsIdentity(scope.Select(s => new Claim(s, s)));
  23.         if (!string.IsNullOrEmpty(userName))
  24.             identity.Claims.Add(new Claim(ClaimTypes.Name, userName));
  25.         httpContext.User = ClaimsPrincipal.CreateFromIdentity(identity);
  26.         Thread.CurrentPrincipal = httpContext.User;
  27.         return base.SendAsync(request, cancellationToken);
  28.     }
  29. }

Inside my Web API, I access the claims information using the standard IClaimsIdentity abstraction.

Accessing claims information
  1. public IEnumerable<string> Get()
  2. {
  3.     if (User.Identity.IsAuthenticated && User.Identity is IClaimsIdentity)
  4.         return ((IClaimsIdentity) User.Identity).Claims.Select(c => c.Value);
  5.     return new string[] { “value1″, “value2″ };
  6. }

Fiddler Testing

Once I got the “access token”, I can test few scenarios in fiddler by attaching and tweaking the token when calling my web api.

401 without an “access token”

image

200 OK with a Valid token

image

401 with Expired token

image

401 with Tempered token

image

Source code attached. Please feel free to download and use.

About these ads

15 Responses to “Claim-based-security for ASP.NET Web APIs using DotNetOpenAuth”

  1. Matt said

    When setting up the issuer, I continue to receive 400 BAD REQUEST {“error”:”invalid_request”}. Any ideas?

  2. Micha Demmers said

    doesn’t work if yo use fiddler to try to get an accesstoken. It doesn’t matter what i do but it keeps giving the response “invalid_request”. It only works when using the sample client written in dotnet. Any ideas?

  3. Nice job done!!

  4. Nice work, but I think this is a demo of oAuth2 “Client Credentials” rather than “Resource Owner Password Credentials”. I think to turn the latter into the former you would need to call ExchangeUserCredentialForToken() on the client and pass the resource owner credentials to get a token. As far as i can see this code will simply verify a client has been registered to access the resource rather than the specific user.

  5. bradirby said

    BTW, you must install the Microsoft Windows Identity Foundation for the sample code to work.
    http://msdn.microsoft.com/en-us/evalcenter/dd440951.aspx

  6. Alex said

    Thanks for the code. It’s going to help tremendously on a current project. I do have one question – you mentioned you were going to use a password grant. But I do not see that anywhere. Shouldn’t the username/password be passed in as form url encoded parameters to the issuer?

  7. Dervanil Junior said

    Hi good sample, but i’m having some trouble, could you help me?
    I need to call the api via JavaScript but i couldn’t find what header i need to set.

  8. Adhikesh said

    Hi zamd,

    I am getting HTTP 400 Bad Request, when giving request from the fiddler
    Please help me

  9. Adhikesh said

    This is good sample, how can we make the request for refresh the token? how the parameters looks like?

  10. [...] short analysis on the “Claim-based-security for ASP.NET Web APIs using DotNetOpenAuth” (http://zamd.net/2012/05/04/claim-based-security-for-asp-net-web-apis-using-dotnetopenauth/?goback=%2…). Few things I tried to short out are listed [...]

  11. [...] An example of “Claim-based-security for ASP.NET Web APIs using DotNetOpenAuth” is available from a Microsoft application development manager Zulfiqar Ahmed at http://zamd.net/2012/05/04/claim-based-security-for-asp-net-web-apis-using-dotnetopenauth/ [...]

  12. Akila Kumarasamy said

    Hi,

    Its a good example, Can I know how to add different claims with the claim type in the OAuthIssuer? Also, the OAuthIssuer throws BAD REQUEST error if the client has the latest nuget package on the DotNetOpenAuth. How can we fix this? Can you please help?

  13. J.M. said

    I was a little confused by the code in the Protocal Independent Issuer. The code is loading up a public and private key. I had thought that OAuth did not require SSL. Do I need to buy certificates?

  14. Prasad said

    Very nice. Everything works as expected. Only thing is if someone wants to check the passed in user creds and the relying party is a complex system like a CMS, it would be tricky to do this.

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

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: