Customizing the Auth flow with Auth0

Using Auth0 for applications is a great way to offload user management and role management to a third party provider which can aid in limiting the blast radius of a breach. While, I will not yet be getting into truly customizing the login experience (UI related) I do want to cover how we can take control of the process to better hide our values and control the overall experience.

You dont control Authentication

In previous posts I discussed the difference between Authentication and Authorization and I bring it up here as well. For applications, Authentication information is the information we least want in the hands of an attacker – being able to log into a system legitimately can make it very hard to track down what has been lost and what has been compromised. This is why authentication mechanisms leveraging OAuth or OpenId rely on performing the authentication OUTSIDE of your system.

By performing the authentication outside of your system and returning a token your site never even see’s the user credentials and you cannot expose what you do not have. Google Pay and other contactless payment providers operate on a similar principal – they grab the credit card number and information for the merchant and pass back a token with the status.

Understanding this principle is very important when designing systems of authentication. Authorization information is less important in terms of data loss but, highly important in terms of proper provisioning and management.

For the remainder of this we will be looking mainly at how to initiate authentication in the backend.

Preparation

For this example, I constructed a simple ASP .NET Core MVC Application and created an Index view with a simple Login button – this could have been a link.


@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
@using (Html.BeginForm("Login", "Home", FormMethod.Post))
{
<input type="submit" value="Login" />
}
</div>

view raw

home.cshtml

hosted with ❤ by GitHub

The goal here is to have a way for the user to initiate the login process. This post will not cover how to customize this Login Screen (hoping to cover that in the future).

Let’s get started

Looking at the code sample above you can see that, submitting the Form goes to a controller called Home and an action called Login. This does not actually submit anything because, remember, Auth0 operates as a third party and we want our users to login and be authenticated there rather than on our site. Our site only cares about the tokens that indicate Auth0 verified the user and their access to the specific application.

Here is the skeleton code for the action that will receive this Post request:


[HttpPost]
public IActionResult Login([FromForm]LoginFormViewModel viewModel)
{
return Ok();
}

view raw

login1.cs

hosted with ❤ by GitHub

This is where the fun begins.

Auth0 Authentication API

OAuth flows are nothing more than a back and forth of specific URLs which first authorize our application request to log in, then authorize the user based on credentials, after which a token is generated and a callback URL is invoked. We want to own this callback URL.

Install the following Nuget package: Auth0.AuthenticationApi – I am using version 7.0.9. Our first step is to construct the Authentication Url which will send us to to Auth0 Lock which handles authentication.


[HttpPost]
public IActionResult Login([FromForm]LoginFormViewModel viewModel)
{
var authClient = new AuthenticationApiClient("<your domain>.auth0.com");
var authUrl = authClient.BuildAuthorizationUrl()
.WithClient("<your client id>")
.WithResponseType(AuthorizationResponseType.Code)
.WithConnection("Username-Password-Authentication")
.WithRedirectUrl("https://localhost:5001/home/callback&quot;)
.WithScope("openid offline_access")
.WithState("1234567890")
.Build()
.ToString();
return Redirect(authUrl);
}

view raw

login2.cs

hosted with ❤ by GitHub

So a few things here:

  • The use of the Client Id indicates to Auth0 for which application we want to authenticate against
  • Response Type is code meaning, we are asking for an authorization code that we can use to get other tokens
  • Connection indicates what connection scope our login can use. Connection scopes indicate where user credential information is stored (you can have multiple). In this case I specify Username-Password-Authentication which will disallow social logins
  • The redirect URI indicates what URL the auth code is passed to. In our case, we want it passed back to us so this URL is for another controller/action combination on our backend. Be sure your Application Settings also specify this URL
  • Scope is the access rights the given User will have. By default we want them to be able to access their information
  • State is a random variable, you will often see it as a value called nonce. This is just a random value designed to make the request unique

After we specify these values and call Build and ToString to get a URL that we can Redirect to. This will bring up the Login Lock screen for Auth0 to allow our user to present their credentials.

Receive the callback

Our next bit is to define the endpoint that will receive the callback from Auth0 when the login is successful. Auth0 will send us a code in the query string that indicates the login was successful.


[HttpGet]
public async Task<IActionResult> Callback([FromQuery]string code)
{
return Ok();
}

view raw

callback.cs

hosted with ❤ by GitHub

This is not atypical for application which use this – if you ever looked at the Angular sample it too provides for a route that handles the callback to receive the code. Once we get the code we can ask for a token. Here is the complete code:


[HttpGet]
public async Task<IActionResult> Callback([FromQuery]string code)
{
var authClient = new AuthenticationApiClient("<domain>.auth0.com");
var tokenResponse = await authClient.GetTokenAsync(new AuthorizationCodeTokenRequest
{
RedirectUri = "https://localhost:5001/home/auth_callbabck&quot;,
Code = code,
ClientId = "<your client id>",
ClientSecret = "<your client secret>"
});
CustomContext.IdToken = tokenResponse.IdToken;
CustomContext.AccessToken = tokenResponse.AccessToken;
CustomContext.RefreshToken = tokenResponse.RefreshToken;
return Redirect("CodeView");
}

view raw

callback2.cs

hosted with ❤ by GitHub

Here we are asking for authorization to the application and it will come with two pieces of information that we want – Access Token and Id Token. The former is what you pass to other APIs that you want to access (your permissions are embedded in this token) and the Id Token represents you user with all of their information.

To aid in what these tokens look like (wont cover Refresh tokens here) I have created this simple custom C# class and Razor view:


namespace Auth0LoginCustom
{
public static class CustomContext
{
public static string AuthCode;
public static string AccessToken;
public static string RefreshToken;
public static string IdToken;
}
}

view raw

context.cs

hosted with ❤ by GitHub


@{
ViewData["Title"] = "Code Page";
}
<div class="text-center" style="width: 500px">
@CustomContext.AuthCode
<hr />
@CustomContext.RefreshToken
<hr />
@CustomContext.IdToken
<hr />
@CustomContext.AccessToken
</div>

view raw

codeview.cshtml

hosted with ❤ by GitHub

Successfully logging in will eventually land you on this Razor page where you should values for everything but AuthCode (there is no set in the code snippet). But something might strike you as weird, why is the access_token so short. In fact, if you run it through jwt.io you may find it lacks any real information.

Let’s explain.

By default Tokens can only talk to Auth0

In previous posts I have discussed accessing APIs using Auth0 Access Tokens. Core to that is the definition of an audience. I deliberately left his code off when we built our Authentication Url as part of login. Without it, Auth0 will only grant access to the userinfo API hosted in Auth0. If we also want that token to be good for our other APIs we need to register them with Auth0 and indicate our UI app can access it.

Before discuss this further, lets update our Auth URL building code as such:


[HttpPost]
public IActionResult Login([FromForm]LoginFormViewModel viewModel)
{
var authClient = new AuthenticationApiClient("<domain>.auth0.com");
var authUrl = authClient.BuildAuthorizationUrl()
.WithClient("<client id>")
.WithResponseType(AuthorizationResponseType.Code)
.WithConnection("Username-Password-Authentication")
.WithRedirectUrl("https://localhost:5001/home/callback&quot;)
.WithScope("openid offline_access")
.WithState("1234567890")
.WithAudience("<api identifier>")
.Build()
.ToString();
return Redirect(authUrl);
}

view raw

auth_final.cs

hosted with ❤ by GitHub

The difference here is Line 12 where we specify our audience. Note that we are NOT allowed to specify multiple audiences so, when designing a microservice API this can be tricky. I want to cover this more in-depth in a later post.

With this in place you will see two things if you run through the flow again:

  • Your access token is MUCH longer and will contain relevant information for accessing your API
  • The refresh token will be gone (I cannot speak to this yet)

This will now return to your call the Access Token you can use and store to access APIs. Congrats.

Why use this?

So when would an approach this like this be practical? I like it for applications that need more security. You see, with traditional SPA application approaches you wind up exposing your client Id and potentially other information that, while not what I could sensitive, is more than you may want to expose.

Using this approach all of the information remains in the backend and is facilitated outside of the users control or ability to snoop.

Conclusion

In this post, I showed how you can implement the OAuth flow yourself using Auth0 API. This is not an uncommon use case and can be very beneficial should your application require tighter control over the process.

3 thoughts on “Customizing the Auth flow with Auth0

  1. Hi,
    Very informative blog. The same I was searching for my project requirement.
    A request from my side, If Possible. Could you please share the sample project of above blog?
    It will help us for integration.

    Thanks,
    Adityanarayan

    Like

Leave a comment