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.
Thanks for the article!
I’m kinda new with the IdentityServer4 I have a question regarding the Client Application startup.cs->Configure->
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
});
For the past few days I always saw example like this here you are setting up the ClientId and the ClientSecret so the way it goes is this will be hardcoded value, how do you do it in a real world application? I’m building right now an application with similar approach and I’m using the Client Model/Class for IdentityServer which will act as the actual client for my app with that the case then it means that my ClientId and ClientSecret will be store in the database but not hardcoded, I hope you can elaborate more and help me on this one? or maybe other that has the same question
Than you in Advance and again great article! will look into the other.
Eds one option is to store this type of information in a database. If you check out this post it covers doing this with Entity Framework.
Glad you are finding the posts helpful. I am pretty new to Identity Server as well and these posts are me documenting what I have learned.
Hello and thanks for this nice series introducing IdentityServer 4.
I have a question related to ASPNET CORE hosting an Angular 4 app using Hybrid Grants.
– Why do I still need to register the Angular app in IdentityServer clients?
– Why do I still need to register the Angular app in IdentityServer as Implicit Grant?
– Isn’t enough to register the MVC app as HybridWithClientCredentials so that no tokens are sent to browser, instead ASPNETCORE will handle things and save the tokens on server-side. Then, any access to any page on Angular app, would be first routed via ASPNETCORE and authenticated, etc.
Thanks
I recommend asking this question in the Identity Server repo or on StackOverflow. I’m still learning all this stuff and don’t feel I can give you the best answer.
Hi Eric,
Thanks for these posts, i’m also getting up to speed with identityserver and was wondering if you’ve uncovered what’s the best Open ID flow to use for SPA(s). Implicit flow seems to the most recommended flow but there are some security concerns and Authorization or Hybrid flows are possible alternatives. Any thoughts based on your findings? Appreciate it.
Glad to know they are helpful, Ivan. Like you in my research implicit flow is the one I found to be recommended for SPAs. I’m not enough of an expert in this area to speak on which flows are or are not secure enough for which uses. I would try StackOverflow tagged with Identity Server. If you do open an issue comment back with a link so others with the same question can see the answers.