Identity Server: From Implicit to Hybrid Flow
This post is a continuation of a series of posts that follow my initial looking into using IdentityServer4 in ASP.NET Core with an API and an Angular front end. The following are the related posts.
Identity Server: Introduction
Identity Server: Sample Exploration and Initial Project Setup
Identity Server: Interactive Login using MVC
Identity Server: From Implicit to Hybrid Flow (this post)
Identity Server: Using ASP.NET Core Identity
Identity Server: Using Entity Framework Core for Configuration Data
Identity Server: Usage from Angularsing MVC
This post is going to cover adding back in the API access that was lost in the last post by changing the MVC client to use a hybrid grant instead of an implicit grant. This post was written while working through Switching to Hybrid Flow and adding API Access back in the official docs.
Identity Application
The changes to the Identity Application are pretty simple and only involve tweaking the settings on the MVC client found in the GetClients function of the Config class. First, change the AllowedGrantTypes from Implicit to HybridAndClientCredentials. Next, a client secret should be added.
ClientSecrets = { new Secret("secret".Sha256()) }
This is, of course, a bad secret, but this is only an example. Next, add “apiApp” to the AllowedScopes and finally add AllowOfflineAccess = true. The following is the full client code.
new Client { ClientId = "mvc", ClientName = "MVC Client", AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { "http://localhost:5002/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "apiApp" }, AllowOfflineAccess = true };
Most of the above are straight forward. AllowedGrantTypes is what is moving to the hybrid flow which then needs a client secret to ensure everything is on the up and up. This client should be able to hit the API application so it is added to the allowed scopes. AllowOfflineAccess is less clear to me. According to the docs, it allows the requesting refresh tokens for long-lived API access. This would take some more digging before production to ensure authorization isn’t too long lived.
Client Application
Changes to the client application were pretty minimal as well. First, in the Configure function of the Startup class, the UseOpenIdConnectAuthentication call must pass a few more items. The following is the full set up.
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions { AuthenticationScheme = "oidc", SignInScheme = "Cookies", Authority = "http://localhost:5000", RequireHttpsMetadata = false, ClientId = "mvc", ClientSecret = "secret", ResponseType = "code id_token", Scope = { "apiApp", "offline_access" }, GetClaimsFromUserInfoEndpoint = true, SaveTokens = true });
ClientSecret should match what was set up for the client in the Identity Application. According to the docs setting ResponseType to code id_token means use a hybrid flow. This is another point that I would want to dig more on. Scope is requesting access to the API Application and offline access which is the matching part to the offline access set up in the Identity Application. GetClaimsFromUserInfoEndpoint tells the middleware to go to the user info endpoint to retrieve additional claims after getting an identity token.
Identity Controller
The Index action in the IdentityController ends up being much simpler than it was in the previous posts. The following is the full function.
[Authorize] public async Task<IActionResult> Index() { var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token"); var client = new HttpClient(); client.SetBearerToken(accessToken); var apiResponse = await client.GetAsync("http://localhost:5001/api/identity"); ViewData["apiResult"] = apiResponse.IsSuccessStatusCode ? await apiResponse.Content.ReadAsStringAsync() : apiResponse.StatusCode.ToString(); return View(); }
In the new version, the token can be retrieved from the HTTP context instead of using the DiscoveryClient and TokenClient like the previous version of this code did. The general idea is the same in both which is to get a token, use the token as part of a request to the API application, and finally display the response in a view.
Identity View
The last set of changes is to the Index.cshtml file in the View/Identity directory which is the view that goes with the Index action of the IdentityController. The view displays the access token, refresh token, results of the API call, and the logged in user’s claims.
@using Microsoft.AspNetCore.Authentication @{ ViewData["Title"] = "Identity"; } <dt>access token</dt> <dd>@await ViewContext.HttpContext.Authentication.GetTokenAsync("access_token")</dd> <dt>refresh token</dt> <dd>@await ViewContext.HttpContext.Authentication.GetTokenAsync("refresh_token")</dd> @ViewData["apiResult"] <h3>User claims</h3> <dl> @foreach (var claim in User.Claims) { <dt>@claim.Type</dt> <dd>@claim.Value</dd> } </dl> <form asp-controller="Identity" asp-action="Logout" method="post"> <button type="submit">Logout</button> </form>
Wrapping up
Adding back API access was pretty easy and the new setup will make managing other resources pretty simple. The identity space is still pretty new to me but working through the IdentityServer quickstarts are helping get me up to a basic level of knowledge. The finished code for this post can be found here. Come back next week to convert this example to use ASP.NET Core Identity.
Identity Server: From Implicit to Hybrid Flow Read More »