Building your own API and Securing it with oAuth 2.0 in ASP.NET WebAPI 2

Objectives:

  1. Make a true RESTful Web API (enable CRUD functions by HTTP POST, GET, PUT, and DELETE).
  2. Enable Cross-Origin Resource Sharing, i.e., CORS (the accessibility of the API by JavaScript can be controlled).
  3. Enable Secure Authorization for API calls (use the OAuth 2.0 authorization framework).
  4. Enable Transport Layer Security, i.e., SSL (reject every non-HTTPS request).

Required Tools:

  1. A Windows Computer.
  2. Microsoft Visual Studio Express 2013 for Web (free download).

Recommended Knowledge:

  1. Securing (ASP.NET) Web API based architectures (video).

AND/OR

  1. ASP.NET Web API 2 (tutorials).
  2. OAuth 2.0 Authorization Framework (specification).
  3. Open Web Interface for .NET (OWIN).
  4. Writing an OWIN Middleware in the IIS integrated pipeline (tutorial).
  5. Enabling Cross-Origin Requests in ASP.NET Web API (tutorial).

An Skeleton Project by ASP.NET Web API 2 (oAuth 2.0 and CORS support)

1. Creating the Project

Create an ASP.NET Web Application (Visual C#) in Microsoft Visual Studio Express 2013 for Web. Let’s name it Skeleton.WebAPI. Select the Web API template and Change Authentication from ‘No Authentication’ to ‘Individual User Accounts’, [screenshot].

The ‘Controllers/ValuesController.cs’ holds our sample API endpoints. Run the project and in the browser go to ‘http://localhost:port/api/values‘ and you should get a 401 Unauthorized.

2. Writing the Sample API Endpoints

In ‘Controllers/ValuesController.cs’, comment out [Authorize] to disable authorization. Run the project and in the browser go to ‘http://localhost:port/api/values‘ and now you should get 2 values.

Change ‘Controllers/ValuesController.cs’ with the following code to enable CRUD operations, [Link]. This controller now accepts HTTP GET, POST, PUT, and DELETE to receive a list of values, add a value, update a value, and delete a value. Currently, these values are stored in the Web Server memory, but ideally they will be saved in the database. I recommend using Repository Pattern for it, [More info].

Let’s write a simple html page to call these api endpoints. For example, the following code can be used for receiving the list of current values:


$.ajax({
type: "GET",
url: 'http://localhost:port/api/values',
success: function (_d) { alert(JSON.stringify(_d));}
}).fail(function (_d) { alert(JSON.stringify(_d)); });

view raw

api_tester.js

hosted with ❤ by GitHub

The full list of API calls by JavaScript can be found here. At this point, you will notice that all the API calls are returning errors. That’s because we haven’t added Cross-Origin Resource Sharing (CORS) support to the server.

3. Enabling Cross-Origin Resource Sharing (CORS) support 

To Enable CORS support, first install Microsoft.AspNet.WebApi.Cors by NuGet. Open up ‘App_Start/WebApiConfig.cs’ and add the following line in the Register method:


public static void Register(HttpConfiguration config)
{
config.EnableCors();
// other stuff
}

view raw

WebApiConfig.cs

hosted with ❤ by GitHub

Then open your API Controller, ‘Controllers/ValuesController.cs’ and add the following line before the class definition:


[EnableCors(origins: "*", headers: "*", methods: "*")]
// [Authorize]
public class ValuesController : ApiController { /*Class Definition*/}

It tells the server the accept all types requests. You can obviously filter out the requests. For more customization, read this.

4. Cleaning up the Views from the project

You should notice that the VS template created some views in the project. As we are building a pure Web API, there will be no views. So, delete App_Start/[BundleConfig,RouteConfig].cs, Areas/*, Content/*, Controllers/HomeController.cs, fonts/*, Scripts/*, Views/*, Project_Readme.html, and favicon.ico. Finally, remove the following lines from Global.asax.cs:


RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);

view raw

Global.asax.cs

hosted with ❤ by GitHub

At this point, we have completed Objectives 1 and 2. The code up to this point can be  downloaded from https://github.com/rfaisal/ASPWebAPI_Example_OAuth_CORs/tree/crud_cors_only_v1.

5. Adding oAuth 2.0 support

In the simplest terms, here is how oAuth (or most token based authorization) works. The client request an Access Token from the Authorization Server. The Authorization Server verifies the identity of the client somehow and returns an Access Token, which is valid for a limited time. The client can use the Access Token to request resources from the Resource Server as long as the Access Token is valid. In our case, both Authorization Server and the Resource Server are the same server, but they can be separated easily.

Ideally, you need to write an OWIN Middleware in the IIS integrated pipeline for oAuth. But, the VS template generates the necessary codes for the OWIN Middleware. Watch this video to learn more about these implementations.

Now, we will add 2 more functionality, namely registering a new user and requesting a Access Token based on the username and password.

The user registration controller (Controllers/AccountController.cs) is already generated by the Template. First add the following line in the AccountController to allow CORS:


[EnableCors(origins: "*", headers: "*", methods: "*")]

Then you can call the end point http://localhost:port/api/Account/Register/ and HTTP POST an object consisting of UserName, Password, and ConfirmPassword to register an account.

Now, the endpoint for requesting an Access Token is also generated by the template. The code is in App_Start/Startup.Auth.cs :


//other stuff
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
//other stuff

view raw

Startup.Auth.cs

hosted with ❤ by GitHub

It says that the client can request an Access Token by calling http://localhost:port/Token endpoint and HTTP POSTing grant_type, username, and password as Url Encoded String. This endpoint, however, is not an API endpoint. To enable CORS to this endpoint we need to add the following code segment to the ConfigureAuth function of App_Start/Startup.Auth.cs file.


public void ConfigureAuth(IAppBuilder app)
{
//other stuff
app.Use(async (context, next) =>
{
IOwinRequest req = context.Request;
IOwinResponse res = context.Response;
if (req.Path.StartsWithSegments(new PathString("/Token")))
{
var origin = req.Headers.Get("Origin");
if (!string.IsNullOrEmpty(origin))
{
res.Headers.Set("Access-Control-Allow-Origin", origin);
}
if (req.Method == "OPTIONS")
{
res.StatusCode = 200;
res.Headers.AppendCommaSeparatedValues("Access-Control-Allow-Methods", "GET", "POST");
res.Headers.AppendCommaSeparatedValues("Access-Control-Allow-Headers", "authorization", "content-type");
return;
}
}
await next();
});
//other stuff
}

view raw

Startup.Auth.cs

hosted with ❤ by GitHub

We are almost done. We have to modify all the API calls to pass this Access Token. First of all, un-comment [Authorize] attribute on the ValuesController and run to make sure that all of the API calls are returning 401 Unauthorized. Add the following property with each ajax call to pass the Access Token as the Bearer Token:


beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", 'Bearer ' + _access_token);
},

view raw

bearer_token.js

hosted with ❤ by GitHub

At this point, we have completed Objectives 1, 2, and 3. The code up to this point can be  downloaded from https://github.com/rfaisal/ASPWebAPI_Example_OAuth_CORs/tree/crud_cors_oauth.

6. Enforce SSL for all API calls

Any information that is not transferred over SSL is not secure. So, we are going to reject any request that doesn’t come over HTTPS. First we will write a filter that can be used to filter out non-https requests. Here is the code:


public class RequireHttpsAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
ReasonPhrase = "HTTPS Required"
};
}
else
{
base.OnAuthorization(actionContext);
}
}
}

When we add this class to our project, we have [RequireHttps] available. Add this attribute to all the Controller class. For example,


[RequireHttps]
[EnableCors(origins: "*", headers: "*", methods: "*")]
[Authorize]
[RoutePrefix("api/Account")]
public class AccountController : ApiController {/*class definition*/}


[RequireHttps]
[EnableCors(origins: "*", headers: "*", methods: "*")]
[Authorize]
public class ValuesController : ApiController {/*class definition*/}

Finally, we also want the Token request to be enforced over HTTPS. To do so, remove ‘AllowInsecureHttp = true’ from Startup.Auth.cs.


OAuthOptions = new OAuthAuthorizationServerOptions
{
//other stuff
TokenEndpointPath = new PathString("/Token")
// AllowInsecureHttp = true
};

view raw

Startup.Auth.cs

hosted with ❤ by GitHub

To test the SSL enforcement, change the SSL Enabled property to true for the Development Server in Visual Studio and use the SSL Url, [screenshot].

The final code can be  downloaded from https://github.com/rfaisal/ASPWebAPI_Example_OAuth_CORs/tree/crud_cors_oauth_ssl.

A screenshot of the client, [screenshot].

Tagged: , , , , , , , , , , , , , , , , , , ,

38 thoughts on “Building your own API and Securing it with oAuth 2.0 in ASP.NET WebAPI 2

  1. Anas KVC April 3, 2014 at 11:49 pm Reply

    Great article.

    To run the sample project in VS 2012 we need to do the following changes.

    1. Add the missing nuget packages using package manager console

    install-package Microsoft.AspNet.Identity.Core
    install-package Microsoft.AspNet.Identity.EntityFramework

    2. Open the local db on vs server explorer.

    It looks like AspNetUsers table doesn’t have the default columns which is mapped from IdentityUser object. (It will throw the error when we try to register a new user)

    So,

    remove the __MigrationHistory table.
    remove the AspNetUsers table and execute the new query on server explorer.

    CREATE TABLE [dbo].[AspNetUsers] (
    [Id] NVARCHAR (128) NOT NULL,
    [Email] NVARCHAR (256) NULL,
    [EmailConfirmed] BIT NOT NULL,
    [PasswordHash] NVARCHAR (MAX) NULL,
    [SecurityStamp] NVARCHAR (MAX) NULL,
    [PhoneNumber] NVARCHAR (MAX) NULL,
    [PhoneNumberConfirmed] BIT NOT NULL,
    [TwoFactorEnabled] BIT NOT NULL,
    [LockoutEndDateUtc] DATETIME NULL,
    [LockoutEnabled] BIT NOT NULL,
    [AccessFailedCount] INT NOT NULL,
    [UserName] NVARCHAR (256) NOT NULL,
    CONSTRAINT [PK_dbo.AspNetUsers] PRIMARY KEY CLUSTERED ([Id] ASC)
    );

    This will resolve the issues.

  2. Faisal R. April 4, 2014 at 6:45 am Reply

    Thanks Anas!

  3. dave April 21, 2014 at 5:38 am Reply

    Is it possible to use Web API 2’s build in external authentication with Angular JS being hosted on a different web-server? If so does anyone know of an example?

    I’m struggling to get it working. The facebook request is giving me a CORS issue. I’m not sure what is going on.

    My only guess is the redirect is coming from a different domain than the web api is hosted on. Which is what is doing the facebook configuration.

    • Faisal R. April 21, 2014 at 6:53 am Reply

      Hi Dave-
      I am assuming you just uncomment app.UseFacebookAuthentication in Startup.Auth.cs and using it the same way the Web API template example is using, except the WebAPI and client is hosted in different servers.

      In that case, adding the domain urls for both your hosts to facebook app settings should work. If not, can you please send me your login flow.

  4. dave April 21, 2014 at 11:02 am Reply

    Sure. My login flow is as follows:

    1.) User navigates to my login page at http://localhost:8000/app/index.html#/login
    2.) On page load I make a request to my web api to load the ExternalLogins at api/Account/ExternalLogins?returnUrl=%2F&generateState=true. This loads the URLS for when the user clicks the login buttons (exactly how its done with Microsoft SPA template)
    3.) When the user clicks login with facebook button, the web api endpoint api/Account/ExternalLogin. The user is not authenticated to the ChallengeResult is returned. This is where the redirect to the facebook login page should happen. I see the request being made in chrome dev tools but the redirect gives me a No Access-Control-Allowed-Origin error.

    I notice the redirect request has a Origin header with a null value. I think this is the problem but I don’t know why its there. I can duplicate the api call in fiddler to my api/Account/ExternalLogin api, but remove the Origin header, and I dont get the No Access-Control-Allowed-Origin error.

    The origin header gets sent to my API from my angular js client, but im not sure why its on the redirect, or why the value is null.

    Any idea on what could be going on? Thanks

  5. dave April 21, 2014 at 11:47 am Reply

    Ok. Thanks for the response. I did find things like this – https://bugs.webkit.org/show_bug.cgi?id=98838

    Not sure if thats the problem.

  6. dave April 22, 2014 at 11:56 am Reply

    Any luck?

    • Faisal R. April 22, 2014 at 11:58 am Reply

      Hi Dave-
      Sorry, I haven’t had a chance to look into this yet. I will do it tonight, please check back tomorrow.

    • Faisal R. April 22, 2014 at 6:02 pm Reply

      Hi Dave-
      I tested it and it worked just fine for me. This is what I did:

      1. Added my facebook appId and appSecret to the project of the blog post. and run it
      2. Added http://localhost:49811/ in my facebook app’s “site url”
      3. Run api/Account/ExternalLogins?returnUrl=%2F&generateState=true to get a list of urls
      4. Created a new SPA project and its login.viewmodel.js–>self.login function –> set window.location to the url I get from Step 3 (hard coded)
      5. Run the SPA and click on the login with facebook button.

      It took me to WebAPI’s root url with a access_token in the url (meaning successful authentication)

  7. dave April 22, 2014 at 7:43 pm Reply

    Faisal,
    I’m going to start fresh and try exactly what you just did. Thanks for the response. I will let you know how it goes.

  8. dave April 22, 2014 at 7:59 pm Reply

    Could you zip up your working solution and email it to me at dmolesky@kent.edu?

  9. Jimmy May 2, 2014 at 4:42 am Reply

    Hello

    is there a second part or has this tutorial changed after getting published ?

    I can’t see any details about DB in here so I’m confused how to authenticate and store user data for this application?

    I can see one comment regarding a table called ‘AspNetUsers’ and EF framework but not mentioned in the tutorial it self

    Cheers

    • Faisal R. May 2, 2014 at 12:45 pm Reply

      Hi Jimmy-
      No there is no 2nd part. You can change the database info in Web.config file. By default, it uses a .mdf file in your local file system for data storage

  10. Alexander Smotritsky (@gyrevik) August 10, 2014 at 8:59 pm Reply

    I think Jimmy may have asked his question because he and other readers may not know that when you create an asp.net web api project you can specify authentication — individual users accounts and this will generate the database/mdf and entity framework layer for it.

  11. mohit September 4, 2014 at 3:29 am Reply

    this post is very helpful
    question 1: But instead of using entity framework …how can we implement it with simple ado.net ?
    question 2 : can it be done without using MVC?

    • Faisal R. September 4, 2014 at 9:41 am Reply

      1. It doesn’t matter what data-source you use and how you use it. It will still work with MySQL, MongoDB and of course, ADO.Net. You need to modify the AccountController.cs file only.

      2. Yes, it can be done. In fact, in the example, there is no view only Model and Controller.

  12. mohit September 4, 2014 at 10:12 pm Reply

    thanks
    i don’t have much knowledge about entity framework
    I am new in this field can you help me by giving an example of above with Ado.net
    in which OAuth Authentication is used with sql server database without entity framework
    if you can do it…..it will be a big help

  13. mohit September 5, 2014 at 12:58 am Reply

    thanks for the reply
    but i don’t have much knowledge about entity framework
    can you help me by giving simple example of above using local database without entity framework or tell me what changes i have to make in order to do so….

  14. Sultan Mehmood September 20, 2014 at 4:12 am Reply

    Hi Faisal,

    I am newbie to Web APIs. I wanted to know if we dont have Owin Middleware, still your code will work ? Because I am trying to run your code and its giving me 404 for Requested URL localhost:(myport)/Token.

    Your response will be highly appreciated.

    Thanks

    • Faisal R. September 20, 2014 at 11:28 am Reply

      I am pretty sure you need OWIN. 404 is a “Not Found” error. Without looking into details I can only advise to make sure that the API project is running.

      • Sultan Mehmood September 20, 2014 at 11:50 am

        Thanks fir your reply. I was able to figure it out.

    • Paco October 7, 2014 at 1:23 pm Reply

      You mentioned you resolved your issue…care to elaborate? I’m having the same issue where I get a 404 when hitting my TokenEndpointPath at /token.

      • Sultan Mehmood October 8, 2014 at 11:56 pm

        Can you please post your code here ? without looking your code I can not tell what exactly you are having….

  15. Julian September 24, 2014 at 1:44 pm Reply

    Simply want to sayy your article is as astounding.

    The clarityy in your publish is jst great and that i caan assume you’re a professional in this subject.
    Fine along with your permission allow me to grasp your feed
    to stay updated with coming neqr near post. Thank you one million and please carry oon the enjoyable work.

  16. monique ma October 8, 2014 at 2:51 pm Reply

    I tried the same structure but using the MVC as test site. When I tried to get token, I got bad request error.
    Code as following:
    public string GetToken(string userName, string userPW)
    {
    var content = “grant_type=password&username=” + userName + “&password=” + userPW;
    string uri = baseUri + “Token”;
    using (HttpClient client = new HttpClient())
    {
    client.BaseAddress = new Uri(baseUri);
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(“application/x-www-form-urlencoded”));
    var response = client.PostAsJsonAsync(uri, content).Result;

    return response.Content.ToString();
    }

    Where is my problem, I need get this token to set further request.
    Like this:
    public Member GetMemberByID (int memberId)
    {
    var token = GetToken(“xinm”, “Test1234″);
    string uri = baseUri +”api/member/170/GetMember”;
    using (HttpClient client = new HttpClient())
    {
    client.BaseAddress = new Uri(baseUri);
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(“application/json”));
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(token);
    Task response = client.GetStringAsync(uri);
    return JsonConvert.DeserializeObjectAsync(response.Result).Result;
    }
    }

  17. youngjaekim November 6, 2014 at 2:13 am Reply

    Thanks for the great guide.
    change to below code for Oauth token.

    if (!string.IsNullOrEmpty(origin))
    {
    res.Headers.Set(“Access-Control-Allow-Origin”, origin);
    res.Headers.Set(“Access-Control-Allow-Credentials”, “true”); // add this line.
    }

  18. gabriel November 26, 2014 at 11:46 am Reply

    Hey, I tried everything until the first milestone (obj 1 and 2) but from Postman extension in chrome the is returning:
    {
    “error”: “invalid_grant”,
    “error_description”: “The user name or password is incorrect.”
    }

    even though I got the correct password, any ideas?

    • gabriel November 26, 2014 at 12:01 pm Reply

      Btw, thats my raw request:

      POST /Token HTTP/1.1
      Host: localhost:49545
      Cache-Control: no-cache
      Content-Type: application/x-www-form-urlencoded

      username=gab&grant_type=password&password=Badu%4000

      • gabriel November 26, 2014 at 12:57 pm

        Ok , I dont know why, but even though I have registered the username as gab, when I request the token, I have to use the username as the value I passed as an Email beacuse the username gab returned that error, but the email did not.
        Any ideas?
        Also how hard would it be to switch that application user manager, and use a legacy authentication model from a existing DB, as that is more realistic, and what I have to do here…

        Thanks, Ill stop bombarding yor blog now…

  19. Luis Martinez March 19, 2015 at 1:26 am Reply

    Thanks a Lot,

    this is the first demo that work for me.

    To be persistent like a mobile application have to be, I need to store the TOKEN in some place in the local divice.

    The authentication and the authorization work very fine. BUT if i copy the Token and try to access to the secure resource in other client, divice or machine, then the web api just give me access to the secure resource.

    Do you understand my concern?, I am very worry about other person can read the token and access to other user profile with out problem.

    So, there it is a way to include other parameter for the authentication and the authorization like macadress or diviceid?.

    However, thanks a lot of the sample, thanks for your help

  20. Cristian June 5, 2015 at 6:32 am Reply

    Hi, using UseOAuthBearerTokens, says “Error 2 ‘Owin.IAppBuilder’ does not contain a definition of ‘UseOAuthBearerTokens’ and no extension method ‘UseOAuthBearerTokens’ accepting a first argument of type ‘Owin.IAppBuilder’ found (are you missing an using directive or an assembly reference) C: \ Users \ crengifo \ documents \ visual studio 2013 \ Projects \ GastosDeViajeWebApi \ GastosDeViajeWebApi \ App_Start \ Startup.Auth.cs 61 17 GastosDeViajeWebApi ”

    You know that it can be ??

  21. Gaurav Kaushik July 15, 2015 at 7:54 am Reply

    Hi Faisal, i have downloaded your code from here (https://github.com/rfaisal/ASPWebAPI_Example_OAuth_CORs/tree/crud_cors_oauth_ssl)
    1. double clicked api_tester.html
    2. when i try to register i am getting this error {“readyState”:0,”responseText”:””,”status”:0,”statusText”:”error”}
    3. i didn’t create any table at back end i am just using the code as it is available.

    plz help its urgent.

  22. Gary August 17, 2015 at 3:32 pm Reply

    Hello,
    I tried to run this example, But I always get this error in chrom browser.

    {“readyState”:0,”responseText”:””,”status”:0,”statusText”:”error”}

    Can you help me out what’s wrong and how can I make it work? Thank you!

  23. Andy September 16, 2015 at 11:03 am Reply

    This works perfectly for me, but how can I extend it to use refresh tokens?

  24. […] I’ve started with this as a base, and it works…but now I need to convert this HTML file to an ASP.NET web app in MVC and figure out where and how to store the access token and refresh tokens. http://blog.rfaisal.com/2014/01/14/building-your-own-api-and-securing-it-with-oauth-2-0-in-asp-net-w…/ […]

  25. […] I’ve started with this as a base, and it works…but now I need to convert this HTML file to an ASP.NET web app in MVC and figure out where and how to store the access token and refresh tokens. http://blog.rfaisal.com/2014/01/14/building-your-own-api-and-securing-it-with-oauth-2-0-in-asp-net-w…/ […]

  26. talented programmer September 24, 2015 at 11:01 am Reply

    I got this site from my buddy who told me concerning this web page
    and now this time I am visiting this site and reading very informative
    posts here.

Leave a reply to Where to store access and refresh tokens on ASP.NET client web app - calling a REST API * Best Wordpress Themes - Reviews Cancel reply