Use OAuth 2.0 to Secure Your ASP.NET Core App

keeping-track-of-things-to-do


Imagine 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:

  1. 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.
  2. 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.
  3. Your app sends the authorization code, the ClientId, and ClientSecret to the TokenEndpoint.
  4. The authorization server returns an access token.
  5. Your app sends the access token to the UserInformationEndpoint.
  6. 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.

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 ClientSecretsettings 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:

As always, if you have comments or questions about this post, feel free to leave them in the comments below. 


Further Reading

 

 

 

 

Top