ASP.NET Core and API access

In the previous quickstarts we explored both API access and user authentication. Now we want to bring the two parts together.

The beauty of the OpenID Connect & OAuth 2.0 combination is, that you can achieve both with a single protocol and a single exchange with the token service.

So far we only asked for identity resources during the token request, once we start also including API resources, IdentityServer will return two tokens: the identity token containing the information about the authentication and session, and the access token to access APIs on behalf of the logged on user.

Modifying the client configuration

Updating the client configuration in IdentityServer is straightforward - we simply need to add the api1 resource to the allowed scopes list. In addition we enable support for refresh tokens via the AllowOfflineAccess property:

new Client
{
    ClientId = "mvc",
    ClientSecrets = { new Secret("secret".Sha256()) },

    AllowedGrantTypes = GrantTypes.Code,
    RequireConsent = false,
    RequirePkce = true,

    // where to redirect to after login
    RedirectUris = { "http://localhost:5002/signin-oidc" },

    // where to redirect to after logout
    PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },

    AllowedScopes = new List<string>
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        "api1"
    },

    AllowOfflineAccess = true
}

Modifying the MVC client

All that’s left to do now in the client is to ask for the additional resources via the scope parameter. This is done in the OpenID Connect handler configuration:

services.AddAuthentication(options =>
{
    options.DefaultScheme = "Cookies";
    options.DefaultChallengeScheme = "oidc";
})
    .AddCookie("Cookies")
    .AddOpenIdConnect("oidc", options =>
    {
        options.Authority = "http://localhost:5000";
        options.RequireHttpsMetadata = false;

        options.ClientId = "mvc";
        options.ClientSecret = "secret";
        options.ResponseType = "code";

        options.SaveTokens = true;

        options.Scope.Add("api1");
        options.Scope.Add("offline_access");
    });

Since SaveTokens is enabled, ASP.NET Core will automatically store the resulting access and refresh token in the authentication session. You should be able to inspect the data on the page that prints out the contents of the session that you created earlier.

Using the access token

You can access the tokens in the session using the standard ASP.NET Core extension methods that you can find in the Microsoft.AspNetCore.Authentication namespace:

var accessToken = await HttpContext.GetTokenAsync("access_token");
var refreshToken = await HttpContext.GetTokenAsync("refresh_token");

For accessing the API using the access token, all you need to do is retrieve the token, and set it on your HttpClient:

public async Task<IActionResult> CallApi()
{
    var accessToken = await HttpContext.GetTokenAsync("access_token");

    var client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    var content = await client.GetStringAsync("http://localhost:5001/identity");

    ViewBag.Json = JArray.Parse(content).ToString();
    return View("json");
}

Create a view called json.cshtml that outputs the json like this:

<pre>@ViewBag.Json</pre>

Make sure the API is running, start the MVC client and call /home/CallApi after authentication.

Managing the access token

By far the most complex task for a typical client is to manage the access token. You typically want to

  • request the access and refresh token at login time
  • cache those tokens
  • use the access token to call APIs until it expires
  • use the refresh token to get a new access token
  • start over

ASP.NET Core has many built-in facility that can help you with those tasks (like caching or sessions), but there is still quite some work left to do. Feel free to have a look at this library, which can automate many of the boilerplate tasks.