Recently I worked with a customer assisting them in implementing their Web APIs using the new ASP. Web API Framework . Their API would is public so obviously security came up as the key concern to address. Claims-based-security was widely used in soap/ws-* world and we had rich APIs available in. NET Framework in the form of W CF, WIF & ADFS 2.0. Even though we now have this cool library to develop Web APIs, the claims-based-security stories for Rest/http are Still catching up. OAuth 2.0 is almost ready, OpenID Connect are catching up quickly however it would still take sometime before we have WIF E Quivalent libraries for implementing claims-based-security on Rest/http World. dotnetopenauth seems to be the M OST prominent Open-source library claiming to support OAuth 2.0 so I decided to give it a go to implement the ' Resour Ce Owner Password Credentials ' authorization grant. Following diagram shows the solution structure for my target scenario.
1. OAuth 2.0 issuer is an ASP 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 is accessed by presenting a valid tokens issued by the trusted issu Er
3. Sample thick client which consumes the Web API
I have used the Dotnetopenauth.ultimate NuGet package which be just a single assembly implementing quite a few security PR Otocols. From OAuth 2.0 perspective, Authorizationserver are the main class responsible for processing the token issuance request, p Roducing 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 Issue rconfiguration { 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 h Andler (Oauth2issuer in following snippet)
Protocol Independent Issuer
public class oauth2issuer:iauthorizationserver{private readonly issuerconfiguration _configuration; Public Oauth2issuer (Issuerconfiguration configuration) {if (configuration = null) throw new argumentnullexcept Ion ("Configuration"); _configuration = Configuration; } public RSACryptoServiceProvider Accesstokensigningkey {get {return (Rsacryptoservicepro Vider) _configuration. Signingcertificate.privatekey; }} public DotNetOpenAuth.Messaging.Bindings.ICryptoKeyStore Cryptokeystore {get {throw new notimplement Edexception (); }} public TimeSpan getaccesstokenlifetime (DotNetOpenAuth.OAuth2.Messages.IAccessTokenRequest accesstokenrequestmes Sage) {return _configuration. Tokenlifetime; } Public iclientdescription Getclient (string clientidentifier) {const string secretpassword = "Test1243″; return new Clientdescription (Secretpassword, New Uri ("http://localhost/"), ClientType.confidential); } Public RSACryptoServiceProvider Getresourceserverencryptionkey ( DotNetOpenAuth.OAuth2.Messages.IAccessTokenRequest accesstokenrequestmessage) {return (RSACryptoServiceProvider ) _configuration. EncryptionCertificate.PublicKey.Key; } public bool Isauthorizationvalid (DotNetOpenAuth.OAuth2.ChannelElements.IAuthorizationDescription authorization) { Claims added to the token authorization. Scope.add ("adminstrator"); Authorization. Scope.add ("PowerUser"); return true; } public bool Isresourceownercredentialvalid (string userName, string password) {return true; } public DotNetOpenAuth.Messaging.Bindings.INonceStore Verificationcodenoncestore {get {t Hrow new NotImplementedException (); } }}
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 following payload
http/1.1 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_ Xocronicpldpgksi5b8egk7dmrrhbswieycx7rldb2l0siw8zwyqtqxofxbcjthjtfahre8owe3hpxur7wmn2lzcityftlkqzw6ujlhev6n4v1hl4md5hdtwy 51_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 ”}
Dotnetopenauth also has a webserverclient class which can is used to acquire tokens and I had used in my test application Instead of crafting raw HTTP requests. Following code snippet generates the same above Request/response
Get Access Token
private static Iauthorizationstate Getaccesstoken () { var authorizationserver = new Authorizationserverdescription { tokenendpoint = new Uri ("Http://localhost:1960/Issuer"), protocolversion = Protocolversion.v20 } ; var client = new Webserverclient (authorizationserver, "http://localhost/"); Client. Clientidentifier = "ZAMD"; Client. Clientsecret = "Test1243″; var state = client. Getclientaccesstoken (new[] {"http://localhost/"}); return state;}
Ok now the 2nd part was to use this access token for authentication & authorization when consuming ASP.
Web API Client
static void Main (string[] args) { var state = Getaccesstoken (); Console.WriteLine ("Expires = {0}", state. ACCESSTOKENEXPIRATIONUTC); Console.WriteLine ("Token = {0}", state.) Accesstoken); var httpClient = new Oauthhttpclient (state. Accesstoken) { baseaddress = new Uri ("Http://localhost:2150/api/values") }; Console.WriteLine ("Calling Web API ..."); Console.WriteLine (); var response = Httpclient.getasync (""). Result; if (response. Statuscode==httpstatuscode.ok) Console.WriteLine (response. Content.readasstringasync (). Result); Else Console.WriteLine (response); Console.ReadLine ();}
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
public class oauthhttpclient:httpclient{Public oauthhttpclient (string accesstoken) : Base (new Oauthtokenhandler (Accesstoken)) { } class Oauthtokenhandler:messageprocessinghandler { string _accesstoken; Public Oauthtokenhandler (String accesstoken) : Base (New Httpclienthandler ()) { _accesstoken = Accesstoken; } protected override Httprequestmessage ProcessRequest (httprequestmessage request, System.Threading.CancellationToken CancellationToken) { request. Headers.authorization = new Authenticationheadervalue ("Bearer", _accesstoken); return request; } protected override Httpresponsemessage ProcessResponse (httpresponsemessage response, System.Threading.CancellationToken CancellationToken) { return response;}} }
Relying party (ASP. NET Web APIs)
Finally on the RP side, I has 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 are passed on The Web API implementation for authorization decisions.
OAuth2 Message Handler
public class oauth2handler:delegatinghandler{private readonly resourceserverconfiguration _configuration; Public Oauth2handler (Resourceserverconfiguration configuration) {if (configuration = null) throw new ARGUMENTN Ullexception ("Configuration"); _configuration = Configuration; } protected override taskInside my Web API, I access the claims information using the standard iclaimsidentity abstraction.
Accessing claims informationPublic ienumerable<string> Get () { if (User.Identity.IsAuthenticated && user.identity are iclaimsidentity) return ((iclaimsidentity) user.identity). Claims.select (c = c.value); return new string[] {"Value1″," Value2″};}
Fiddler TestingOnce 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"$ OK with a Valid token401 with Expired token401 with tempered TokenSource code attached. Feel free to download and use.
Original Post by zulfiqarahmed on may4th, 2012
here:http://zamd.net/2012/05/04/claim-based-security-for-asp-net-web-apis-using-dotnetopenauth/
claim-based-security for ASP. NET Web APIs using Dotnetopenauth