This article was featured as a Technical Tuesday article on the Microsoft MVP Program Blog
There are many tutorials out there that discuss the ease of setting up a new project and checking all of the magic boxes to add Identity, WebApi controllers, and more. However, while these may be helpful, in real-world situations are often not as simple. We might have existing projects that at the start didn’t need WebAPI - or maybe we used WebApi controllers in our code - but all in all, we didn’t get the proper security architecture in place.
In this post, we will walk through how to enhance an existing project to be able to create WebApi controllers and properly secure them using OAuth.
Prerequisites
This article assumes that you have an existing ASP.NET MVC project with ASP.NET Identity configured as part of the solution. If you do not yet have Identity configured, you will need to add this portion to your project before we get started.
If your project has already been configured for WebAPI, and is just missing Authentication, I will try to call attention to the areas you can skip.
Getting Started
Before we get into the specific changes, I encourage you to commit any unsaved changes and be set up to roll back should any of these steps not work as expected. We will try to approach the needed configuration items in a logical manner that will minimize errors, but anything can happen!
Validate NuGet Packages
First, validate that you have all of the proper NuGet packages added to your solution. This will ensure that you can complete the remaining steps of the article without issue. The table below outlines the specific packages that are needed.
Package Name | Purpose |
---|---|
Microsoft.Owin.Security.OAuth |
Implementation of OAuth services. This is the middleware that will handle the token generation |
Microsoft.Owin.Cors |
It allows you to manage Cors security for client-side requests and validation of proper domains. (This is outside the scope of this article, however, included for proper configuration.) |
Microsoft.AspNet.WebApi.Core |
Core package for WebApi (Might already be added) |
Microsoft.AspNet.WebApi.Owin |
Connector for hosting WebApi using Owin |
If any of these packages are missing from your installation you can use “Install-Package {packageName}” from the Package Manager Console. It is possible that additional packages will be added as dependencies. This would be a good time as well to make sure that you have all of the proper versions inside your project.
Configuration of WebAPI & CORS
The next step of the process is to ensure that we have a proper configuration for WebAPI and Cors. These are high-level configuration items. If you already have a WebApi controller in your project, it is possible that you might have many of these items defined.
Add Web API Configuration
Now that we have the proper NuGet packages, we want to create our configuration class. This should go inside of your App_Start folder, just like the other configuration items - such as BundleConfig or RouteConfig. The following snippet is an example of a basic configuration for WebAPI
There are a few key things we can take from this default configuration. The first line of code removes any default authentication that may be defined at the host level. This will ensure that when the second line of code adds an authentication filter for OAuth that it will be the only one applied.
After this, we add the needed configuration for the routing. First, we enable HttpAttributeRoutes, as well as define the default routes. The style of routing used will depend on your desired architecture.
Update Startup.cs
The next steps in configuring the application is to update Startup.cs to add the configuration of CORS. We also need to register the WebApi Middleware within the Owin context. In our case, the final contents of Startup.cs, with the existing ConfigureAuth call was similar to the code below:
In this example, we have the configuration defined for CORS - however, it is set to allow all callers. If you want, feel free to send detailed policy information rather than a blanket accept. We then let the App know that we want to use WebAPI.
Update Global.asax.cs
The final piece of the operation is to configure WebApi, which is part of the regular ASP.NET Pipeline. Although this will function if placed in other locations, it is important to register this here for future enhancement, such as the use of Swagger for Api documentation. Do this by simply adding one line of code inside of the Application_Start method:
If the above line of code does not work in your project, ensure that you have included the Microsoft.AspNet.WebApi.WebHost NuGet package.
I also find that when looking for configuration items later, it is easier to remember that routing configuration hooks are completed in the Global.asax file, rather than Startup.cs.
Configure OAuth Authentication
The final steps are to set up the application to authenticate and issue credentials for user accounts.
Update User Object
A small change needs to be made to your ASP.NET Identity User object, to add an overload allowing you to pass through the authentication type to the CreateIdentityAsync method. This code sample assumes that your user object is named ApplicationUser.
In this example, we take the existing GenerateUserIdentityAsync method and have it call a new overload with an additional parameter. You could also use an optional parameter; the key consideration is that we must be able to generate an identity while passing the authentication type as a parameter.
Add an ApplicationOAuthProvider
The final step for implementation requires us to create an Application OAuth Provider. This is a default implementation that outlines how to authenticate and login a user through the process. There are two classes needed to create this object.
ChallengeResult.cs
This result is used to communicate authentication challenge responses within the actual provider.
ApplicationOAuthProvider
This is the actual implementation of your provider. The key concepts here are that we need to properly configure the application to validate passed username & password information to authenticate as a user within our system. Add:
In this example we have added the minimum implementation using basic considerations with the out-of-the-box authentication configuration.
Enable OAuth in Startup.Auth
The final step to enable OAuth is to update your Startup.Auth to include the oAuth items. Two properties need to be added first, one to store the options and the other to store a public client id.
After this has been added we add the configuration to actually enable OAuth for the project. It is important to note that the “AuthorizeEndpointPath” property isn’t used within the actual configuration at this point as we are using tokens, rather than external authentication grants. This block of code should be added to the constructor, along with the rest of the initialization code.
When configuring in your environment, pay special attention to the expiration timespan, as well as the enabling of insecure Http. For demo purposes we allow non-HTTPS traffic, but you shouldn’t within your application.
Validate Operations
Now that we have everything properly configured, we just need to make a simple API to validate that all operations were successful.
Sample ApiController
This is a simple controller, which includes only a simple route, and the Authorize Attribute to ensure that security is validated.
Test Requests
If you attempt to make a get request to {siteUrl}/Test/HelloWorld you should get a 401: Not Authorized response.
Obtain Bearer Token
You should then be able to make a post request to {siteUrl}/Token, with x-www-form-urlencoding parameters including:
Key | Value |
---|---|
grant_type | Should always be set to "password" without the quotes |
username | The API User account name |
password | The password for the API account |
This will provide a JSON object with the following fields.
FieldDescription & Type access_tokenThe access token. This is the value that will be included on each API call token_typeThe type of token. It will always be a string value expires_inThe number of seconds until the token expires usernameThe username as authenticated .issuedThe timestamp for issue .expiresThe timestamp for expiration
Test Another Request
If you reattempt the request for the HelloWorld action, and this time provide an Authorization header with a value of “Bearer {access_token}” you should get a successful result.
Summary
With just a few short steps, you can easily add OAuth security to your existing - or new - WebApi controllers. This allows your API’s to be consumed in a common manner, without requiring substantial effort on your part. After this initial setup you can easily customize the process for any application specific requirements.