http://blog.geveo.com/OAuth-Implementation-for-WebAPI2

OAuth is an open standard for token based authentication and authorization on internet. In simple terms OAuth provides a way for applications to gain credentials to other application without directly using user names and passwords in every requests. This is to reduces the chances of user name and passwords getting compromised in the wire and improve the security.

This post explains how to implement the OAuth token based authentication mechanism to your Web API methods.

Let’s take an example where several third party connected apps try to connect to a web api with the given client id and client secret. Imagine the scenario where all the connected apps will have background processes to get and post data through the web api and there are no users involved. So in this case it uses client id and client secret to generate a token. The following diagram explains the process.

Now as you have an understanding of what the business requirement is Let’s look into the code how we can implement the token based authentication to the web api solution.

First Create the Web API project through Visual Studio. (File -> New -> Project)

Then we need to install the required packages to the project.

Microsoft.Owin.Security.OAuth

Then we can create our partial startup class as follows.

  1. namespace Demo.WebApi
  2. {
  3.  
  4. public partial class Startup
  5. {
  6.  
  7. public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
  8.  
  9. static Startup()
  10. {
  11. OAuthOptions = new OAuthAuthorizationServerOptions
  12. {
  13. TokenEndpointPath = new PathString("/oauth/token"),
  14. Provider = new OAuthAppProvider(),
  15. AccessTokenExpireTimeSpan = TimeSpan.FromDays(General.AccessTokenExpiryDays),
  16. AllowInsecureHttp = General.UseHttp
  17. };
  18.  
  19. }
  20.  
  21. public void ConfigureAuth(IAppBuilder app)
  22. {
  23. app.UseOAuthBearerTokens(OAuthOptions);
  24. }
  25. }
  26. }

In the above code Token end point refers to the relative path of your web api which needs to be called to generate the token. In this case it is “oauth/token”

Provider is the custom class that we write with the authentication and token generation logic.

Access Token Expire Time Span refers to the duration we want to keep the token alive. We can define this in the web config file and refer it from there. In the above code General class property reads the web config value.

Allow Insecure Http refers whether we want to restrict the web api to be accessed through secured layer or not.

Then from the main start up class we can call the above ConfigureAuth method as follows.

  1. namespace Demo.WebApi
  2. {
  3. public partial class Startup
  4. {
  5. public void Configuration(IAppBuilder app)
  6. {
  7. ConfigureAuth(app);
  8. }
  9. }
  10. }

The general class would look like this.

  1. public class General
  2. {
  3. public static bool UseHttp
  4. {
  5. get
  6. {
  7. if (ConfigurationManager.AppSettings["UseHttp"] != null)
  8. {
  9. return Convert.ToBoolean(ConfigurationManager.AppSettings["UseHttp"]);
  10. }
  11. else return false;
  12. }
  13. }
  14. }

Next, we will see how to implement the OAuthProvider Class. Basically in that class we can override the methods so that we can define the way we want to generate the token.

The following class implementation represents how to implement the token generation using client id and client secret. (Apart from this we can override the GrantResourceOwnerCredentials() in case we want to use username,password combination for generating the password).

First we need to override the ValidateClientAuthentication method. From this we extract the client id and client secret in the request.

  1. namespace IPG.WebApi.Provider
  2. {
  3. public partial class OAuthAppProvider : OAuthAuthorizationServerProvider
  4. {
  5. public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
  6. {
  7. string clientId;
  8. context.TryGetFormCredentials(out clientId, out clientSecret);
  9. if (!string.IsNullOrEmpty(clientId))
  10. {
  11. context.Validated(clientId);
  12. }
  13. else
  14. {
  15. context.Validated();
  16. }
  17. return base.ValidateClientAuthentication(context);
  18. }
  19. }
  20. }

Next we will override GrantClientCredentials method inside the same class. Here we validate the client id and client secret with the DB and create the token. For the token we can add claims as we want. In this case we add the client id only. This claims will be accessible via the context object in the subsequent requests.

  1. public override Task
  2. GrantClientCredentials(OAuthGrantClientCredentialsContext context)
  3. {
  4. return Task.Factory.StartNew(() =>
  5. {
  6. try
  7. {
  8. bool isValid = false;
  9. isValid = true; //This should be the Service/DB call to validate the client id, client secret.
  10. //ValidateApp(context.ClientId, clientSecret);
  11.  
  12. if (isValid)
  13. {
  14. var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
  15. oAuthIdentity.AddClaim(new Claim("ClientID", context.ClientId));
  16. var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
  17. context.Validated(ticket);
  18. }
  19. else
  20. {
  21. context.SetError("Error", tuple.Item2);
  22. logger.Error(string.Format("GrantResourceOwnerCredentials(){0}Credentials not valid for ClientID : {1}.", Environment.NewLine, context.ClientId));
  23. }
  24. }
  25. catch (Exception)
  26. {
  27. context.SetError("Error", "internal server error");
  28. logger.Error(string.Format("GrantResourceOwnerCredentials(){0}Returned tuple is null for ClientID : {1}.", Environment.NewLine, context.ClientId));
  29. }
  30. });
  31. }

Then, we will look at how to authorize the controller actions using this token. It is simple, all you need to do it decorate the action with the [Authorize] tag. This will check token validity before its executes the action method. Following is an example. Create a controller called “PropertyController” and inside that you can define an action as below.

  1. public class PropertyController : ApiController
  2. {
  3. [Authorize]
  4. [HttpGet]
  5. public IHttpActionResult GetProperty(int propertyID)
  6. {
  7. int clientID = OwinContextExtensions.GetClientID();
  8. try
  9. {
  10. //var result = Service or DB Call(clientID, propertyID)
  11. return Json(new
  12. {
  13. PropertyName = string.Format("Property - {0}", propertyID),
  14. Success = true
  15. });
  16. }
  17. catch (Exception ex)
  18. {
  19. return Content(HttpStatusCode.InternalServerError, ex.Message);
  20. }
  21. }
  22. }

Here you can extract the claims that you have added to the token. In our example it was only the client id. You can write a separate class for these types of extractions. Following shows that extension class.

  1. public static class OwinContextExtensions
  2. {
  3. public static int GetClientID()
  4. {
  5. int result = ;
  6. var claim = CurrentContext.Authentication.User.Claims.FirstOrDefault(c => c.Type == "ClientID");
  7.  
  8. if (claim != null)
  9. {
  10. result = Convert.ToInt32(claim.Value);
  11. }
  12. return result;
  13. }
  14.  
  15. public static IOwinContext CurrentContext
  16. {
  17. get
  18. {
  19. return HttpContext.Current.GetOwinContext();
  20. }
  21. }
  22. }

That’s pretty much it. Now we can test the web api using postman as shown below.

We need to enter the correct url. It should be the “websiteurl/oauth/token”. The relative path should matched with the token end point that we have configured in the oauth options. Once the client id and secret is validated access token is generated as above.

We can use this token in order to call other api methods which we have decorated with [authorize] attribute. In our example we are going to call the GetProperty action in our Property controller using that token. The way to call your action using postman is shown below.

You need to use Authorization tag and as the value (Bearer “token”). This is the token generated in the previous step.

There you go. You have the response. Simple as that. You can write your own controllers and actions and build a complete web api from here onwards.

In my next post I will share how we can integrate swagger and directly call our web api using that without using postman.

实际使用遇到的问题

grant type的问题

错误1

配置好之后,访问http://localhost/Chuck_WebApi/oauth/token

request

GET /Chuck_WebApi/oauth/token HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
cache-control: no-cache
Postman-Token: 548a3363-f40e-41fe-b6ed-0529cb446f6e

response

{
"error": "unsupported_grant_type"
}

错误2

request2

GET /Chuck_WebApi/oauth/token HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
cache-control: no-cache
Postman-Token: db016f42-ff23-437d-a820-d2c6604be2ec
grant_type=passwordusername=adminpassword=passwordundefined=undefined

response2

{
"error": "invalid_grant"
}

尝试切换到client_credentials才能成功访问

request3

GET /Chuck_WebApi/oauth/token HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
cache-control: no-cache
Postman-Token: fd3aee1e-97e0-485a-aebf-21f6ae7099e4
grant_type=client_credentialsusername=adminpassword=passwordundefined=undefined

response3

{
"access_token": "Z8CxAgt-vosdjPG5tKUCZoiw7OyW0kCqz9a-U2tCtU2z_-UxZjhGW8AvzqBYPZomiRa8tegKCvVyRVzI-EWmLUJkkaIgjtsse16pVvGISHKs90EqOZxtKppaJbbMn7bCEJwp4npxa9DnlMbhTiNLviRzFvo5wONCNhB1NvN71b8g3rw8ehBZ6TSfJuTzv8OsCisyUV_QwwKQ_nECs07kYQ",
"token_type": "bearer",
"expires_in": 86399
}

授权

所有的controller默认都需要授权

https://stackoverflow.com/questions/21916870/apply-authorize-attribute-implicitly-to-all-web-api-controllers

Token路径如何触发的

https://stackoverflow.com/questions/23215672/in-web-api-owin-architecture-where-are-requests-to-token-handle

When you create a new Project with Individual Authentication in ASP.NET, the solution is created with an OAuth Provider to handle Authentication Request.

If you look at you solution, you should see a Providers Folder with a class ApplicationOAuthProvider.

This class implement all the logic for authenticate your members in you website. The configuration is set at Startup to allow you to customize the url endpoint through the OAuthOption.

  1. OAuthOptions = new OAuthAuthorizationServerOptions
  2. {
  3. TokenEndpointPath = new PathString("/Token"),
  4. Provider = new ApplicationOAuthProvider(PublicClientId),
  5. AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
  6. AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
  7. AllowInsecureHttp = true
  8. };

The TokenEndPoint Path properties defined the url which will fired the GrantResourceOwnerCredentials method of the GrandResourceOwnerCredentials.

If you use fiddler to authenticate and use this kind of body

  1. grant_type=password&username=testUserName&password=TestPassword

you should pass in the following method :

  1. public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
  2. {
  3. var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
  4. ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
  5. if (user == null)
  6. {
  7. context.SetError("invalid_grant", "The user name or password is incorrect.");
  8. return;
  9. }
  10. ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
  11. OAuthDefaults.AuthenticationType);
  12. ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
  13. CookieAuthenticationDefaults.AuthenticationType);
  14. AuthenticationProperties properties = CreateProperties(user.UserName);
  15. AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
  16. context.Validated(ticket);
  17. context.Request.Context.Authentication.SignIn(cookiesIdentity);
  18. }

where context.UserName and context.Password are set with the data used in the request. After the identity is confirmed (here using Entity Framework and a couple userName, Password in a database), a Bearer token is sent to the caller. This Bearer token could then be used to be authenticated for the other calls.

OAuth Implementation for ASP.NET Web API using Microsoft Owin.的更多相关文章

  1. [转] JSON Web Token in ASP.NET Web API 2 using Owin

    本文转自:http://bitoftech.net/2014/10/27/json-web-token-asp-net-web-api-2-jwt-owin-authorization-server/ ...

  2. JSON Web Token in ASP.NET Web API 2 using Owin

    In the previous post Decouple OWIN Authorization Server from Resource Server we saw how we can separ ...

  3. 购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(3)--Idetity,OWIN前后端验证

    原文:购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(3)--Idetity,OWIN前后端验证 chsakell分享了前端使用AngularJS,后端使用ASP. ...

  4. ASP.NET Web API与Owin OAuth:使用Access Toke调用受保护的API

    在前一篇博文中,我们使用OAuth的Client Credential Grant授权方式,在服务端通过CNBlogsAuthorizationServerProvider(Authorization ...

  5. 在ASP.NET Web API 2中使用Owin OAuth 刷新令牌(示例代码)

    在上篇文章介绍了Web Api中使用令牌进行授权的后端实现方法,基于WebApi2和OWIN OAuth实现了获取access token,使用token访问需授权的资源信息.本文将介绍在Web Ap ...

  6. 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 ...

  7. ASP.NET Web API Authorization using Tokens

    Planning real world REST API http://blog.developers.ba/post/2012/03/03/ASPNET-Web-API-Authorization- ...

  8. 在ASP.NET Web API 2中使用Owin基于Token令牌的身份验证

    基于令牌的身份验证 基于令牌的身份验证主要区别于以前常用的常用的基于cookie的身份验证,基于cookie的身份验证在B/S架构中使用比较多,但是在Web Api中因其特殊性,基于cookie的身份 ...

  9. ASP.NET Web API的安全管道

    本篇体验ASP.NET Web API的安全管道.这里的安全管道是指在请求和响应过程中所经历的各个组件或进程,比如有IIS,HttpModule,OWIN,WebAPI,等等.在这个管道中大致分两个阶 ...

随机推荐

  1. CSS 中 BEM命名方式

    BEM的意思就是块(block).元素(element).修饰符(modifier),是一种CSS Class 命名方法. 类似于: .block{} .block__element{} .block ...

  2. Abp Uow 设计

    初始化入口 在AbpKernelModule类中,通过UnitOfWorkRegistrar.Initialize(IocManager) 方法去初始化 /// <summary> /// ...

  3. varints

    Protocol Buffer技术详解(数据编码) - Stephen_Liu - 博客园 https://www.cnblogs.com/stephen-liu74/archive/2013/01/ ...

  4. 给input文本框添加灰色提示文字,三种方法.

    1.这个是HTML5的属性. h5的好简单.... placeholder="这里输入文字" 2.HTML的: value="你的提示文字" onFocus=& ...

  5. c++主程这种事情,就是这样,看人先看人品,没人品,他的能力与你何关?

    这就是人品的重要性........ 接手别人的代码,说困难,也困难,说容易也容易 想把别人代码都读通,理顺,在改原代码BUG,在完美的加功能,那项目越大,越难 想把别人代码里面,加点坑,随便找个模块, ...

  6. Python并行编程(四):线程同步之RLock

    1.基本概念 如果想让只有拿到锁的线程才能释放该锁,那么应该使用RLock()对象.当需要在类外面保证线程安全,又要在类内使用同样方法的时候RLock()就很使用. RLock叫做Reentrant ...

  7. Mybatis的MapperRegistry错误

    1,如果mabtis的配置文件里mapper用的不是包扫描,而是: <mapper resource="com/vmpjin/mapper/OrdersMapper.xml" ...

  8. java生成多位随机数方法

    Math.random()方法可以令系统随机选取大于等于0.0且小于1.0的伪随机double值 利用函数Math.random()即可生成若干位随机数 以下是生成十位随机数代码: public st ...

  9. 记CM+kerberos环境停电后无法启动报错An error: (java.security.PrivilegedActionException: javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided (Mechanism leve

    公司突然停电,然后cm环境无法重启,报错 An error: (java.security.PrivilegedActionException: javax.security.sasl.SaslExc ...

  10. 脚本其实很简单-windows配置核查程序(1)

    先上成品图 需求描述 我们电脑上都安装各种过监控软件,比如360.鲁大师等等...其中有一个功能就是性能监控,在安全行业里面通常叫做"配置核查",目的就是将主机的各种性能指标展示, ...