Use OAuth 2.0 to Secure Your ASP.NET Core App
Secure your ASP.NET application with OAuth.
Join the DZone community and get the full member experience.
Join For FreeImagine having an app where you can write and store your notes efficiently. Today, we are going to build an app that will keep track of your notes. We’ll use ASP.NET Core to build the app. We’ll also use .NET Core’s OAuth 2.0 authentication middleware to make sure the personal notes are kept secure.
My Private Notes App
As mentioned earlier, you'll use an ASP.NET app to build your note-keeping app. Here's how the app works: The home page will keep track of all your recent notes, and if you include more than three notes, the oldest will be shelved. Once we've built the app, you'll learn how to secure it with OAuth. Read this starter project from GitHub to get started.
You may also like: API Security: Ways to Authenticate and Authorize.
Run the project to make sure it starts. You should see a "Hello, World" message displayed and some other basic app scaffolding.
Start by replacing the contents of Controllers\HomeController.cs
with the code below.
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace OAuthNotes.Controllers
{
public class HomeController : Controller
{
private static List<string> _notes;
public HomeController()
{
if (_notes == null)
{
_notes = new List<string> {"", "", ""};
}
}
public IActionResult Index()
{
return View(_notes);
}
[HttpPost]
public IActionResult Add(string note)
{
_notes.Add(note);
if (_notes.Count > 3)
{
_notes.RemoveAt(0);
}
return RedirectToAction("Index");
}
}
}
As you can see, notes are kept in a static list, which is initialized the first time the controller is loaded. Then, there are methods to show the list and add items to it.
Next, you need to replace the contents of Views\Home\Index.cshtml
with this code:
@model List<string>
@{
ViewData["Title"] = "My Notes";
}
<h1>My Notes</h1>
<ul>
@foreach (var note in Model)
{
<li>@note</li>
}
</ul>
<form asp-action="Add" method="POST">
<input type="text" name="note" />
<input type="submit" value="Add Note" />
</form>
Now, the home page should allow you to add up to three notes. That was easy, wasn’t it? But what if you don’t want curious eyes looking at your notes? How can you keep your notes private? Let’s use OAuth 2.0 to secure access to the app.
Set up ASP.NET OAuth 2.0 Authentication Middleware
OAuth 2.0 is a popular security protocol used by many organizations to protect sensitive systems and information. Many websites use OAuth to allow users to sign into their applications and other people’s applications.
ASP.NET Core comes with OAuth authentication middleware, which makes it easy to use a third-party OAuth 2.0 server for login. Many social networks and websites provide an OAuth 2.0 service for public use, so regardless of whether you want to log in with Facebook, BitBucket, Stack Overflow, or Trello, it’s just a matter of setting them up as the Identity Provider.
For this tutorial, you will use Okta's OAuth service to protect your app. The ASP.NET OAuth Middleware will be connected to Okta and use Okta as the Identity Provider. One neat feature of Okta’s service is that it can federate many different authentication services and provide your app just one point of integration for them all.
First, you’ll need to open up Startup.cs
and add this line right above app.UseMvc
in the Configure
method:
app.UseAuthentication();
Then, add this at the top of the ConfigureServices
method:
services.AddAuthentication(options =>
{
// If an authentication cookie is present, use it to get authentication information
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
// If authentication is required, and no cookie is present, use Okta (configured below) to sign in
options.DefaultChallengeScheme = "Okta";
})
.AddCookie() // cookie authentication middleware first
.AddOAuth("Okta", options =>
{
// Oauth authentication middleware is second
var oktaDomain = Configuration.GetValue<string>("Okta:OktaDomain");
// When a user needs to sign in, they will be redirected to the authorize endpoint
options.AuthorizationEndpoint = $"{oktaDomain}/oauth2/default/v1/authorize";
// Okta's OAuth server is OpenID compliant, so request the standard openid
// scopes when redirecting to the authorization endpoint
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
// After the user signs in, an authorization code will be sent to a callback
// in this app. The OAuth middleware will intercept it
options.CallbackPath = new PathString("/authorization-code/callback");
// The OAuth middleware will send the ClientId, ClientSecret, and the
// authorization code to the token endpoint, and get an access token in return
options.ClientId = Configuration.GetValue<string>("Okta:ClientId");
options.ClientSecret = Configuration.GetValue<string>("Okta:ClientSecret");
options.TokenEndpoint = $"{oktaDomain}/oauth2/default/v1/token";
// Below we call the userinfo endpoint to get information about the user
options.UserInformationEndpoint = $"{oktaDomain}/oauth2/default/v1/userinfo";
// Describe how to map the user info we receive to user claims
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "given_name");
options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
// Get user info from the userinfo endpoint and use it to populate user claims
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = JObject.Parse(await response.Content.ReadAsStringAsync());
context.RunClaimActions(user);
}
};
});
I added a lot of comments in the code you just pasted in to help you understand what the middleware is doing, but I’ll also describe it step-by-step here.
Enforce OAuth Authorization Code Flow
If an unauthenticated user tries to access a URL that requires authorization, the authentication middleware will be triggered. In this case, it will use the Okta OAuth service, since the DefaultChallengeScheme
is set to "Okta"
.
The OAuth middleware will kick off the OAuth 2.0 authorization code flow, which works like this:
- Your app redirects the user to the
AuthorizationEndpoint
where they can authenticate (sign in with a username and password) and authorize the app to get access to the requested resources. In this case, we request access to some identity information, including the user’s name and email address, via some predefined scopes. - The authorization server redirects the user back to your app’s
CallbackPath
with an authorization code in the URL. The middleware intercepts this request and gets the authorization code. - Your app sends the authorization code, the
ClientId
, andClientSecret
to theTokenEndpoint
. - The authorization server returns an access token.
- Your app sends the access token to the
UserInformationEndpoint
. - The authorization server returns the identity information that was requested.
Once the flow is complete, the middleware in your app maps the identity information it received to claims and creates a secure cookie to save the authenticated user’s information. On subsequent requests, the user identity is populated from the cookie, saving all of the back-and-forth communication between your app and the authentication server.
Now, you just need to add an [Authorize]
attribute right above the HomeController
class in Controllers\HomeController.cs
so that only authenticated users can access the app.
Configure the Authorization Server in Okta
Although you have set up the app to authenticate with Okta, Okta won’t recognize your app until you register it.
Sign in to your Okta domain if you already have an account or sign up now for a forever-free developer account if you don’t.
Once you’re signed in to Okta, register your client application.
- In the top menu, click on Applications.
- Click on Add Application.
- Select Web and click Next.
- Enter
ListApp
for the Name. - Change the Base URIs to the exact URL that your application runs on locally, including the trailing backslash. This should be something like
https://localhost:44377/
(change the port number to match your port number and make sure it is https). - Change the first of the Login redirect URIs to have the same scheme, host, and port number as above. It should still end with
authorization-code/callback
- Click Done.
On the next screen, you will see an overview of settings. Below, the General Settings section, you’ll see the Client Credentials section. Note the Client ID and the Client Secret on the next page and add them to your appsettings.json
file, like this:
"Okta": {
"ClientId": "{yourOktaClientId}",
"ClientSecret": "{yourOktaClientSecret}",
"OktaDomain": "https://{yourOktaDomain}"
}
Your Okta Domain is the Org URL displayed in the top left corner of the main Okta Dashboard page.
(Note that in order to keep these secrets out of source control you should move the ClientId
and ClientSecret
settings to your User Secrets file before you commit. You can skip this step for now since this is just a tutorial.)
Now, you should be all set. When you run your app, Okta should prompt you to sign in. After signing in, you will be able to access your private notes.
If you want to keep your notes truly private, you will need to adjust the HomeController
to maintain separate lists for each authenticated user. For example, you could create a dictionary of user lists, using the unique identifier in User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value
as a dictionary key. I’ll leave that coding to you.
Limitations of OAuth 2.0
Although the OAuth protocol can be used for user authentication, it wasn’t actually designed for it. The OAuth protocol was designed for delegated access. The access tokens that are issued by OAuth servers are like hotel key cards. They grant access to certain rooms, but they often don’t have any identifying information attached. Of course, the staff at the front desk of a hotel will probably require you to present identification before they hand out a key card, but each hotel’s process could be a bit different.
As people began to use OAuth for authentication, there were a variety of different ways that the authentication process was handled. For example, there is no standard way to do a logout process with OAuth. The app you just created clears a local cookie when you click on Sign out, but you are still signed in at the Okta server, so if you click Sign in again you will be automatically signed in again without being prompted for a password! (If you want, you can close your browser to clear Okta’s cookie.)
To overcome the confusion of using OAuth for authentication without having a shared standard for how to use it, the OpenID Connect standard was built on top of OAuth. If you’re interacting with an OAuth authorization server that also supports OpenID Connect (like Okta), using the .NET Core OpenID Connect middleware (or Okta’s even simpler OpenID Connect middleware) will save you a lot of effort. See the links below for more information on how to use OpenID Connect for authentication in your app.
Learn More About OAuth 2.0 and ASP.NET
Interested in learning more about ASP.NET Core, Oauth 2.0, OpenID Connect, or building secure applications with Okta? Check out our Product Documentation or any of these great resources:
- What is OAuth?
- OAuth 2.0 and OpenID Connect.
- OpenID Connect for User Authentication in ASP.NET Core.
- Create Login and Registration in Your ASP.NET Core MVC App.
- Add Login to Your ASP.NET Core MVC App.
As always, if you have comments or questions about this post, feel free to leave them in the comments below.
Further Reading
Published at DZone with permission of Lindsay Brunner, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments