Displaying Auth0 user profiles in ASP.NET Core 6.0 (part 2)

Table of contents

Earlier I showed you how to Display Auth0 user profiles in ASP.NET Core 6.0

but did not show you how to automatically renew the token.

If you haven’t noticed yet you’ll see you can’t get the users and will be returned to this exception.

`ErrorApiException: {"statusCode":401,"error":"Unauthorized","message":"Expired token received 
for JSON Web Token validation","attributes": {"error":"Expired token received for JSON Web 
Token validation"}}`

If we did it the same way we did it in part one then we would have to manually regenerate the token in Auth0 dashboard and hardcoded it again!

Or…

We could request a refresh token from the Auth0 Dashboard and use that to generate a brand new token automatically.

Also we left the token string in our project tree which can be dangerous if you commit the token to source control. You want to declare it outside of your project tree using either the ASP.NET Secrets Manager or an environment variable. You will learn this for this blog.

Before starting

You should have used the Auth0 quickstart for this.

You can also use my complete project from the part 1 branch which is a companion for this part2 tutorial and continue using that to follow along.

The complete code for this part 2 project is in this repository in case you get stuck

In this blog you will update your previous logic and use new technologies.

Install Auth0 Authentication API SDK

We need access to the ClientCredentialsTokenRequest Class which lies in the Auth0.AuthenticationApi. Download this into your project to access the library.

dotnet add package Auth0.AuthenticationApi --version 7.7.0

In .NET 6 Specifying your project name prevents confusion where C# advises it found more than one namespace in the same directory and doesn’t know which namespace to look in for your project. All namespaces would have to be the same. The only way it knows otherwise is by specifying the project name when you have different grouped namespaces.

Copy these library folders from repository into your project

Please, star this repository if you are logged in and clone it!

It is a version of the Identity Model library we will be using to renew the token automatically.

Make sure you have cloned and extracted the repository. The folders you need to copy across from the repository are:

  • AccessTokenManagement
  • Services

Fix namespaces

Now that you have done that, you will get errors in ASP.NET that will lie in Services/IUserService.cs and Services/UserService.cs. To resolve them take a look at the 2 interfaces just mentioned. You will see that ASP.NET doesn’t know wether you want to make a HTTP request to a User model in your ViewModels directory or Auth0.ManagementApi.Models.User.

All you want to do is rename the IPagedList in IUserService.cs to:

Task<IPagedList<Auth0.ManagementApi.Models.User>> GetUsersAsync(GetUsersRequest request, PaginationInfo paginationInfo,
        CancellationToken cancellationToken);

And then in UserService.cs just rename it to:

public async Task<IPagedList<Auth0.ManagementApi.Models.User>> GetUsersAsync(GetUsersRequest request,
    PaginationInfo paginationInfo, CancellationToken cancellationToken)
{
    return await MakeCallAsync(async apiClient => await apiClient.Users.GetAllAsync(request, paginationInfo),
        cancellationToken);
}

Delete the Services folder namespaces that both interfaces lie in or simply rename them to something else. I have simply deleted them.

Paste new Auth0ManagementApi and Identity Model logic into our Home Controller

Go into Data\GlobalNamespaces and declare these three models at the top of your code above the namespace.

global using Auth0.ManagementApi.Paging;
global using Example.Auth0.AuthenticationApi.Services;
global using System.Threading;
global using Auth0.ManagementApi.Models;

We need to create a list and tell C# that we are using IUserService. So in the controller create an IPagedList, declare that you are using the IUserService and pass it to the constructor. Also use the CancellationToken class to cancel retrieval of Auth0 refresh token if unsuccessful. It should be in System.Threading.

public IPagedList<Auth0.ManagementApi.Models.User> Users { get; private set; }
private readonly IUserService _userService;

public HomeController(IUserService userService)
{
    _userService = userService;
}

Now let’s get to the action method being passed to the view which is GetAllAuth0Users()

This is what the action method looked like in part one of this tutorial.

public async Task <IActionResult> GetAllAuth0Users()
{
    //Get token
    var apiClient = new ManagementApiClient(Auth0UserProfileDisplayStarterKit.ViewModels.ConstantStrings.strToken, new Uri ("https://dev-dgdfgfdgf324.au.auth0.com/api/v2/"));
    //Get all auth0 users that have this application
    var allUsers = await apiClient.Users.GetAllAsync(new Auth0.ManagementApi.Models.GetUsersRequest(), new Auth0.ManagementApi.Paging.PaginationInfo());
    //Read each Auth0 user by field
    var renderedUsers = allUsers.Select(u => new User
    {                
        //Split their full name into first and last name if there is a space
        UserFirstName = u.FullName.Contains(' ') ? u.FullName.Split(' ')[0] : "no space",
        UserLastName = u.FullName.Contains(' ') ? u.FullName.Split(' ')[1] : "no space",
        //Get user profile email
        UserContactEmail = u.Email
    }).ToList();

    return Json(renderedUsers);
}

Now change it to this.

/// <summary>
/// Display Auth0 users from list.
/// </summary>
/// <param name="cancellationToken">Notify application any action should be canceled</param>
/// <returns></returns>
public async Task <IActionResult> GetAllAuth0Users(CancellationToken cancellationToken)
{            
    //Get new token using Auth0ManagementApi with IUserService to interface with Identity Model library.
    var allUsers = await _userService.GetUsersAsync(new GetUsersRequest(), new PaginationInfo(), cancellationToken);
    //Get all auth0 user's first name, last name and email
    var renderedUsers = allUsers.Select(u => new Auth0UserProfileDisplayStarterKit.ViewModels.User
    {                
        UserFirstName = u.FullName.Contains(' ') ? u.FullName.Split(' ')[0] : "no space",
        UserLastName = u.FullName.Contains(' ') ? u.FullName.Split(' ')[1] : "no space",
        UserContactEmail = u.Email
    }).ToList();
    return Json(renderedUsers);
}

A lot of it is self explanatory from the comments I wrote. The big changes are we are no longer using the Auth0ManagementApiClient class methods or the constant string we made earlier. So you can delete that ConstantStrings class.

We are instead using the Auth0ManagementApi methods to get the Auth0 dashboard Users Profiles we have connected in our Auth0 Dashboard application. And we are using the IUserService which we declared in the constructor to interface with the Identity Model library and get us a refresh token.

We also get the CancellationToken using this method.

public async Task OnGet(CancellationToken cancellationToken)
{
    Users = await _userService.GetUsersAsync(new GetUsersRequest(), new PaginationInfo(), cancellationToken);
}

Get Access Token

Now create two models in your Data folder to create the access tokens. Here is the code for LoginAuthentication.cs. The purpose of the static constructor was so we could allow appsettings.json to communicate the key value pair of “AccessTokenManagement:Audience” with our static method Auth0Token. I will show how we create this value and store it later.

using System.IO;
using Auth0.AuthenticationApi;
using Microsoft.Extensions.Configuration;
namespace Auth0UserProfileDisplayStarterKit.Data
{
    public class LoginAuthentication
    {
       //Allow value keys pair access to this class.
      public static IConfiguration AppSetting { get; }

        //Setup static constructor to get uri from appsettings
        static LoginAuthentication()
        {
            AppSetting = new ConfigurationBuilder()
                    .SetBasePath(Directory.GetCurrentDirectory())
                    .AddJsonFile("appsettings.json")
                    .Build();
        }
        public static Auth0Token Login(string ClientID, string ClientSecret, string domain, string Audience)
        {
            var authenticationApiClient = new AuthenticationApiClient(domain);
            var token =  authenticationApiClient.GetTokenAsync(new Auth0.AuthenticationApi.Models.ClientCredentialsTokenRequest
            {                
                ClientId = ClientID,
                ClientSecret = ClientSecret,
                //Get uri using the static constructor.
                Audience = LoginAuthentication.AppSetting["AccessTokenManagement:Audience"],
            }).Result;
            return new Auth0Token {strAuth0Token = token.AccessToken};   
        }
    }
}

Make token globally accessible

Add the 2nd model to make our token globally accessible throughout our client side application.

    public class Auth0Token
    {
        public string strAuth0Token {get;set;}
    }

Now we need to add AccessTokenManagement references in Program.cs. Code looks like this.

// Add the Auth0 HttpClientManagementConnection.
builder.Services.AddSingleton<IManagementConnection, HttpClientManagementConnection>();
// Add JWT renewal references  
builder.Services.AddAccessTokenManagement(builder.Configuration); 
builder.Services.AddTransient<IUserService, UserService>();

And for that AccessTokenManagement reference to work we will have to add the using statement for it

using Example.Auth0.AuthenticationApi.AccessTokenManagement;

Create machine to machine application and Authorise an API

Now we need to login to the Auth0 dashboard server and create a machine application to act as an API between the Auth0 server and our ASP.NET client application.

In the Auth0 Dashboard go to:

  1. Applications
  2. Create Application
  3. Select Machine to Machine Applications. We will call it “User_Profile_Service.”
  4. In the “Authorize Machine to Machine Application”, select the API in the list. If you don’t know which to use that’s ok. Just use the default Auth0 API which is called Auth0 Management API.
  5. Hit the Create button.
  6. Now from the list of permissions select “read:users” and hit authorize.

Create_Machine_To_Machine_App

Notice how you can see the ClientSecret? That is something you should not be able to see. Luckily I have rotated it which can be done in the client application.

Bind Auth0 API keys to ASP.NET Secret Manager

Speaking of ClientSecrets, did you know we can keep the information out of source control without renaming the property values to nonsensical names in appsettings.json?

The answer is to use the ASP.NET Core Secret Manager. Warning, this is where it can get frustrating if done wrong, advise you backup your project now.

Open appsettings.json. Do not set any values yet. Just notice how dangerous it is right now to put especially our client secret (private key) in the file and put it in a public repo.

  "Auth0": {    
    "ManagementApi": 
      "BaseUri": "{BASE_URI}",
      "Domain": "{DOMAIN}",
      "ClientId": "{CLIENT_ID}",
      "ClientSecret": "{CLIENT_SECRET}"
  }

Look for the Auth0 server application you have made for this client application. Can you see the values for the Domain? ClientID? ClientSecret. You should know all this if you’ve read the Auth0 Quickstart for ASP.NET 6.

Also you need the value for the Auth0 Management API. To find the value on the dashboard go to: -Applications -APIs -And you should see the value right there.

Get_ManagementAPI_value

I will show you how to set the values for those 4 properties outside of the project quickly and easily so the API keys stay on your local environment away from the hackers prying eyes.

Fire up the .NET CLI and initialize the Secret Manager Tool which should appear with a unique ID in your project file.

dotnet user-secrets init

Do NOT copy and paste this into terminal! Write each value into these properties by hand so as to insert your own values.

dotnet user-secrets set "Auth0:Domain" "INSERT DOMAIN VALUE HERE WITH NO https:// IN FRONT!" --project Auth0UserProfileDisplayStarterKit.csproj
dotnet user-secrets set "Auth0:ClientId" "INSERT CLIENTID VALUE HERE!" --project Auth0UserProfileDisplayStarterKit.csproj
dotnet user-secrets set "Auth0:ClientSecret" "INSERT CLIENTSECRET VALUE HERE!" --project Auth0UserProfileDisplayStarterKit.csproj
dotnet user-secrets set "Auth0:ManagementApi:BaseUri" "INSERT THE VALUE FOR THE AUTH0 MANAGEMENT API VALUE HERE!" --project Auth0UserProfileDisplayStarterKit.csproj

If you mess up, please click here to understand how to remove the user secrets and start over. Now run the code to ensure no runtime error occurs that stops the screen from showing.

The data in the table won’t load yet and you should get one or two of these errors in the properties inspector.

Failed to load resource: the server responded with a status of 500 (Internal Server Error)

jquery.dataTables.js:6522 Uncaught Error: DataTables warning: table id=auth0UsersTable - Ajax error. For more information about this error, please see http://datatables.net/tn/7

All you need to know is that while we have inserted the keys to connect Auth0 to our client app, we have not inserted the values for the API Explorer Application which will allow us to connect the Auth0 Management API to the client.

In appsetting.json insert this block that we call our AccessTokenManagement code. You can insert it just above your Auth0 code block.

  "AccessTokenManagement": {
    "Domain": "{DOMAIN}",
    "Clients": [
      {
        "Name": "UserService",
        "ClientId": "{CLIENT_ID}",
        "ClientSecret": "{CLIENT_SECRET}",
        "BaseUri": "{BASE_URI}"
      }
    ]
  },

Setting these properties is going to look different from the previous code block so here is how to implement it in the terminal. Please keep in mind we need to set AccessTokenManagement:Domain with https in front even though it doesn’t say that in the Machine to Machine application. That’s how it’s set in the Quick Start. If you’re not sure, always check how the values are set in the Quick Start.

dotnet user-secrets set "AccessTokenManagement:Domain" "INSERT DOMAIN VALUE HERE WITH https:// IN FRONT!" --project Auth0UserProfileDisplayStarterKit.csproj
dotnet user-secrets set "AccessTokenManagement:Clients:0:ClientId" "INSERT CLIENTID VALUE HERE!" --project Auth0UserProfileDisplayStarterKit.csproj
dotnet user-secrets set "AccessTokenManagement:Clients:0:ClientSecret" "INSERT CLIENTSECRET VALUE VALUE HERE!" --project Auth0UserProfileDisplayStarterKit.csproj
dotnet user-secrets set "AccessTokenManagement:Clients:0:Audience" "INSERT THE VALUE FOR THE AUTH0 MANAGEMENT API VALUE HERE!" --project Auth0UserProfileDisplayStarterKit.csproj

Again the --project parameter and specifying the project file is needed because not doing so makes .NET warn us that there are multiple project files found because of the Identity Model namespaces we imported before.

Now debug the application. The code should show that we are still getting the same user profiles as in the previous blog for this series. Only this time we don’t have to manually reset the JWT after it has expired.

In our next blog we will look at storing our JWT in a distributed cache service to help us improve the performance and scalability of our ASP.NET 6 MVC client application and store it in a database. Part3 blog here!