Marrying Web App and Web API - ob1dev/Auth0 GitHub Wiki

The very last step is to connect everything together. The Web App needs to obtain the Access Token issued for the Web API, as well as scopes for API permissions. Once you have all of these, you're good to start signing requests with the header Authorization when calling Web API endpoints.

Visual Studio

In the project OneGit.Web, in the files appsettings.json and appsettings.Development.json, add the following settings using the values from your Auth0 API.

{
  "Auth0": {
    "ApiIdentifier": "https://onegit-webapi.azurewebsites.net/api/",
    "ApiScopes": [ "read:repositories", "create:repositories", "update:repositories", "delete:repositories" ]   
  }
}

In the project OneGit.Web, in the file Startup.cs, modify the method ConfigureServices as shown below:

Add audience

To obtain the Access Token, pass the parameter audience with Auth0 API identifier.

public void ConfigureServices(IServiceCollection services)
{
  ...
  .AddOpenIdConnect("Auth0", options =>
  {
    options.Events = new OpenIdConnectEvents
    {
      ...
      OnRedirectToIdentityProvider = context =>
      {
        context.ProtocolMessage.SetParameter("audience", this.Configuration["Auth0:ApiIdentifier"]);
        return Task.FromResult(0);
      }
    };
  });
  ...
}

Add scopes

Request all the scopes that you defined for Auth0 API, such read:repositories, create:repositories, update:repositories and delete:repositories.

public void ConfigureServices(IServiceCollection services)
{
  ...
  .AddOpenIdConnect("Auth0", options =>
  {
    ...
    var apiScopes = string.Join(" ", this.Configuration.GetSection("Auth0:ApiScopes").GetChildren().Select(s => s.Value));
    options.Scope.Add(apiScopes);
    ...
  });
  ...
}

Extend profile with

In the project OneGit.Web, modify the class UserProfileModel under the folder Models as shown below.

public class UserProfileModel
{
  ...
  public string AccessToken { get; set; }
}

Modify the view Profile under the folder Views\Account as shown below.

...
<p>
  <i class="glyphicon glyphicon-lock"></i> <code>access_token</code>
  <div>
    <mark>@Model.AccessToken</mark>
  </div>
</p>

Modify the class AccountController under the folder Controllers, by extendiong the action Profile.

[Route("[controller]/[action]")]
public class AccountController : Controller
{
...
  [HttpGet]
  public async Task<IActionResult> Profile()
  {
    return View(new UserProfileModel()
    {
      ...
      AccessToken = await HttpContext.GetTokenAsync("access_token")
    });
  }
}

Test Access Token

Run the Web App and sign in using either user ([email protected] or [email protected] or [email protected]). Then navigate to the page /Account/Profile. There you should find both tokens id_token and access_token.

The access_token is what you need to use when sending requests to Web API. But let's look inside it first. So, copy its value and past in the field Encoded at the URL https://jwt.io/.

In the field PAYLOAD:DATA you will find the claim scope, with all scopes the Web App is configured to obtain in the method ConfigureServices.

{
  "scope": "openid profile email create:repositories read:repositories update:repositories delete:repositories"
}

You might spot that by default, any user gets the Access Token with all scopes defined in Auth0 API. But, this behavior is not what you really want to. It would make more sense to have access policies to limit scopes by roles, which you already defined before (viewer, editor and admin). This can be archive using Auth0 Rules.

Rules

In the Auth0 Portal, go to the section Rules at the right menu. Click the button + Create Rule.

Use the template empty rule and add the following code.

function (user, context, callback)
{
  // Add the library Lodash - https://lodash.com/docs/4.17.10
  var _ = require("lodash");
  
  // Current request.
  var request = context.request;
  
  // Get requested scopes.
  var scopes = (request.query && request.query.scope) || (request.body && request.body.scope);
  
  // Normalize scopes into an array.
  scopes = (scopes && scopes.split(" ")) || [];
  
  // Restrict the access token scopes according to the current user's role.
  context.accessToken.scope = restrictScopes(user, scopes);
  
  callback(null, user, context);
  
  function restrictScopes(user, requested) 
  {
    // Full list of available scopes.
    var all = ["test:repositories", "create:repositories", "read:repositories", "update:repositories", "delete:repositories"];
    
    var adminRoleScope = ["create:repositories", "read:repositories", "update:repositories", "delete:repositories"];
    var editorRoleScope =  ["create:repositories", "read:repositories", "update:repositories"];
    var viewerRoleScope = ["read:repositories"];

    var metadata = user.app_metadata || {};
    if (!metadata.roles)
    {
      console.log("The user \'" + user.email + "\' has no role. Access denied.");
      return callback(new UnauthorizedError("No roles defined. Access denied."));
    }
    
    var userRoles = metadata.roles;
    var allowed;
    
    if (userRoles.indexOf('admin') !== -1)
    {
      allowed = adminRoleScope;
    } 
    else if (userRoles.indexOf('editor') !== -1)
    {
      allowed = editorRoleScope;
    }
    else if (userRoles.indexOf('viewer') !== -1)
    {
      allowed = viewerRoleScope;
    }
    
    console.log("Requested scope is: \'" + requested + "\'");
    console.log("Allowed scope is: \'" + allowed + "\'");
    
    var result = _.difference(requested, all);
    console.log("Difference: \'" + result + "\'");
    
    result = _.union(result, allowed);
    console.log("Union: \'" + result + "\'");
    
    return result;
  }
}

Test Web API endpoints

Using tool Postman call some of the Web API endpoints using the Access Token as a bearer token in the header Authorization.

GET https://onegit-webapi.azurewebsites.net/api/repositories
authorization: Bearer {your-access-token-here}

Summary

You've now configured the Web App to obtain the Access Token for specific Web API, and set up the rule to return a set of scopes based on user's role. Now the Web App has everything to start sending requests to protected Web API endpoints by signing them with JWT Bearer token.

I used the HTTPClient factory to wrap Web API endpoints. You can see implementation details here.

And I used Swagger for Web API to make it easy for exploring.

What's next?

This completes this series of tutorials on integrating the Auth0 in an ASP.NET Web Application and ASP.NET Web API using ASP.NET Core 2.1.

If I missed anything or didn't provide enough details, feel free to explore the source code of the Web App and Web API in this repository, or file an issue to ask me a question, or provide some suggestion.

You also welcome to play with my Web App and Web API using the following URLs:

I appreciate you feedback, and thank you for following my tutorial!

⚠️ **GitHub.com Fallback** ⚠️