Claim-based-security for ASP.NET Web APIs using DotNetOpenAuth
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.

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)
public class OAuth2Issuer : IAuthorizationServer
{
private readonly IssuerConfiguration _configuration;
public OAuth2Issuer(IssuerConfiguration configuration)
{
if (configuration == null) throw new ArgumentNullException(“configuration”);
_configuration = configuration;
}
public RSACryptoServiceProvider AccessTokenSigningKey
{
get
{
return (RSACryptoServiceProvider)_configuration.SigningCertificate.PrivateKey;
}
}
public DotNetOpenAuth.Messaging.Bindings.ICryptoKeyStore CryptoKeyStore
{
get { throw new NotImplementedException(); }
}
public TimeSpan GetAccessTokenLifetime(DotNetOpenAuth.OAuth2.Messages.IAccessTokenRequest accessTokenRequestMessage)
{
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
{
throw 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 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”}

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
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 is to use this access token for authentication & authorization when consuming ASP.NET Web APIs.
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
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 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.
public class OAuth2Handler : DelegatingHandler
{
private readonly ResourceServerConfiguration _configuration;
public OAuth2Handler(ResourceServerConfiguration configuration)
{
if (configuration == null) throw new ArgumentNullException(“configuration”);
_configuration = configuration;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpContextBase httpContext;
string userName;
HashSet<string> scope;
if (!request.TryGetHttpContext(out httpContext))
throw new InvalidOperationException(“HttpContext must not be null.”);
var resourceServer = new ResourceServer(new StandardAccessTokenAnalyzer(
(RSACryptoServiceProvider)_configuration.IssuerSigningCertificate.PublicKey.Key,
(RSACryptoServiceProvider)_configuration.EncryptionVerificationCertificate.PrivateKey));
var error = resourceServer.VerifyAccess(httpContext.Request, out userName, out scope);
if (error != null)
return Task<HttpResponseMessage>.Factory.StartNew(error.ToHttpResponseMessage);
var identity = new ClaimsIdentity(scope.Select(s => new Claim(s, s)));
if (!string.IsNullOrEmpty(userName))
identity.Claims.Add(new Claim(ClaimTypes.Name, userName));
httpContext.User = ClaimsPrincipal.CreateFromIdentity(identity);
Thread.CurrentPrincipal = httpContext.User;
return base.SendAsync(request, cancellationToken);
}
}
Inside my Web API, I access the claims information using the standard IClaimsIdentity abstraction.
public IEnumerable<string> Get()
{
if (User.Identity.IsAuthenticated && User.Identity is IClaimsIdentity)
return ((IClaimsIdentity) User.Identity).Claims.Select(c => c.Value);
return new string[] { “value1″, “value2″ };
}
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”

200 OK with a Valid token

401 with Expired token

401 with Tempered token

Source code attached. Please 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的更多相关文章
- ASP.NET Web APIs 基于令牌TOKEN验证的实现(保存到DB的Token)
http://www.cnblogs.com/niuww/p/5639637.html 保存到DB的Token 基于.Net Framework 4.0 Web API开发(4):ASP.NET We ...
- 基于.Net Framework 4.0 Web API开发(4):ASP.NET Web APIs 基于令牌TOKEN验证的实现
概述: ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作.但是在使用API的时候总会遇到跨域请求的问题, ...
- 基于.Net Framework 4.0 Web API开发(2):ASP.NET Web APIs 参数传递方式详解
概述: ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作.调用API过程中参数的传递是必须的,本节就来谈谈 ...
- 基于.Net Framework 4.0 Web API开发(3):ASP.NET Web APIs 异常的统一处理Attribute 和统一写Log 的Attribute的实现
概述: ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作.但是项目,总有异常发生,本节就来谈谈API的异常 ...
- 基于.Net Framework 4.0 Web API开发(5):ASP.NET Web APIs AJAX 跨域请求解决办法(CORS实现)
概述: ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作.但是在使用API的时候总会遇到跨域请求的问题,特 ...
- ASP.NET Web API 2 external logins with Facebook and Google in AngularJS app
转载:http://bitoftech.net/2014/08/11/asp-net-web-api-2-external-logins-social-logins-facebook-google-a ...
- 在ASP.NET Web API 2中使用Owin OAuth 刷新令牌(示例代码)
在上篇文章介绍了Web Api中使用令牌进行授权的后端实现方法,基于WebApi2和OWIN OAuth实现了获取access token,使用token访问需授权的资源信息.本文将介绍在Web Ap ...
- 在ASP.NET Web API 2中使用Owin基于Token令牌的身份验证
基于令牌的身份验证 基于令牌的身份验证主要区别于以前常用的常用的基于cookie的身份验证,基于cookie的身份验证在B/S架构中使用比较多,但是在Web Api中因其特殊性,基于cookie的身份 ...
- ASP.NET Web API安全认证
http://www.cnblogs.com/codeon/p/6123863.html http://open.taobao.com/docs/doc.htm?spm=a219a.7629140.0 ...
随机推荐
- 【Linux常识篇(3)】文件及文件夹的ctime atime mtime的含义详解
首先可以使用stat 命令来查询文件的inode信息,其中包括ctime atime mtime [root@localhost ~]# stat sort2.txt File: 'sort2.txt ...
- 函数执行到return就结束了
遇到return,函数就结束了,不会往下执行 测试: class User { String name; int age; boolean fun1(int i){ if(i==1){ return ...
- Android 时间轴TimeLine
代码:这里
- 机器学习 —— 概率图模型(推理:MAP)
MAP 是最大后验概率的缩写.后验概率指的是当有一定观测结果的情况下,对其他随机变量进行推理.假设随机变量的集合为X ,观察到的变量为 e, W = X-e , AP = P(W|e). 后验概率和联 ...
- junit浅学笔记
JUnit是一个回归测试框架(regression testing framework).Junit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(Wh ...
- [Codeforces677B]Vanya and Food Processor(模拟,数学)
题目链接:http://codeforces.com/contest/677/problem/B 题意:n个土豆,每个土豆高ai.现在有个加工机,最高能放h,每次能加工k.问需要多少次才能把土豆全加工 ...
- 在Windows下通过命令行或者.bat文件统计一个目录中文件数量
在Windows下面怎样通过命令行统计一个目录中文件的数量,或者说,如果在一个.bat文件中,统计一个目录中的文件数量? 我原来以为是不可能的,要编一个vbs程序什么的,后来到网上找了下,发现还真是可 ...
- Oracle默认的用户名和密码
你是说默认的用户名和密码么scott 密码是 tigersys 密码是 change_on_installsystem 密码是 managersysman 密码是 oem_temp 其中直接管理模式可 ...
- UVa 10088 (Pick定理) Trees on My Island
这种1A的感觉真好 #include <cstdio> #include <vector> #include <cmath> using namespace std ...
- UVa 11178 (简单练习) Morley's Theorem
题意: Morley定理:任意三角形中,每个角的三等分线,相交出来的三个点构成一个正三角形. 不过这和题目关系不大,题目所求是正三角形的三个点的坐标,保留6位小数. 分析: 由于对称性,求出D点,EF ...