Identity Server: Sample Exploration and Initial Project Setup
This post will be a continuation of my exploration around Identity Server which was started with this post which was more of an overview of the space and my motivations for learning about Identity Server. There were a lot of things that were unclear to me as I first started looking through the samples so this post is going to communicate so of those issues and hopefully clear them up for you as well.
After this post, the follow-up post should be more focused on one thing instead of trying to cover so much information in on go.
The following are all the posts in this series.
Identity Server: Introduction
Identity Server: Sample Exploration and Initial Project Setup (this post)
Identity Server: Interactive Login using MVC
Identity Server: From Implicit to Hybrid Flow
Identity Server: Using ASP.NET Core Identity
Identity Server: Using Entity Framework Core for Configuration Data
Identity Server: Usage from Angular
Typical Sample Solution Structure
I started my exploration with the IdentityServer4.Sample repo specifically in the Quickstarts folder. For me, this was a mistake as I didn’t have a good enough grasp on the larger concepts for the code to provide proper guidance. Big surprise here, but using the docs and actually walking through the project creation was very helpful.
The code associated with this blog can be found here. The solution contains three projects.
- ApiApp – Backend for the application and is a resource that is will require authorization to access. The API will be an ASP.NET Core Web API.
- ClientApp – Frontend application that will be requesting authorization. This is an ASP.NET Core application that is hosting an Angular (4) app.
- IdentityApp – This is ASP.NET Core application that is the IdentityServer and will end up authorizing users and issuing tokens for resources.
Identity Application
For the Identity application, create an ASP.NETÂ Core Web Application using the empty template targeting at least ASP.NET Core 1.1. Next, using NuGet install the IdentityServer4 NuGet package.
Now that the IdentityServer4 NuGet package is installed open up the Startup class and add the following to the ConfigureServices function.
public void ConfigureServices(IServiceCollection services) { services.AddIdentityServer() .AddTemporarySigningCredential() .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()); }
The above registers IdentityServer with ASP.NET Core as a service available via dependency injection using temporary and in-memory components as a stand in for testing. The Config class used here will be added a bit later.
Next, the Configure function should look like the following.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseDeveloperExceptionPage(); app.UseIdentityServer(); }
The above is a basic Configure function. app.UseIdentityServer() is the only the only bit related to IdntityServer and it is adding it to the application’s pipeline.
The final part of this application is the Config class which is used to define the in memory test resources for this application. As you can see below it is defining both API resources and Clients. In the future theses, items would be pulled from a datastore.
public class Config { public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource("apiApp", "API Application") }; } public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientId = "clientApp", // no interactive user, use the clientid/secret for authentication AllowedGrantTypes = GrantTypes.ClientCredentials, // secret for authentication ClientSecrets = { new Secret("secret".Sha256()) }, // scopes that client has access to AllowedScopes = { "apiApp" } } }; } }
API Application
For the API application, create an ASP.NETÂ Core Web Application using the Web API template targeting at least ASP.NET Core 1.1. Next, using NuGet install the IdentityServer4.AccessTokenValidation NuGet package.
Ater the above NuGet package installed open up the Startup class. In the Configure function, the IdentityServer middleware needs to be added to the application pipeline before MVC using the app.UseIdentityServerAuthentication function. The following is the full Configure function.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions { Authority = "http://localhost:5000", RequireHttpsMetadata = false, ApiName = "apiApp" }); app.UseMvc(); }
Notice that the address of the of the authority must be specified (this will need to be the address the Identity Application is running on) as well as the ApiName matches the API Resource we added in the Config class of the Identity Application.
Next, in following the IdentityServer quickstart docs add a new IdentityController to the project. Just to be 100% clear this is just a test endpoint to show how to require authorization on a controller and isn’t something that is required to use IdentityServer. The controller has a single Get that returns the type and value of all the user’s claims.
[Route("api/[controller]")] [Authorize] public class IdentityController : Controller { [HttpGet] public IActionResult Get() { return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); } }
The [Authorize] attribute on the class is the bit that requires calls to this endpoint to have authorization. Keep in mind that the same attribute can be left off the class level and added to specific functions if the whole controller doesn’t require authorization.
Adding the [Authorize] attribute means that the IdentityServer middleware we added in the Startup class will validate the token associated with the request to make sure it is from a trusted issues and that it is valid for this API.
Client Application
For the Client application, I deviated from the samples a bit. Instead of just creating a new MVC application I used JavaScriptServices to generate an Angular (4) application. If you want detail on how that is done you can check out this post (yes it says Angular 2, but the newest version of JavaScriptServices now outputs Angular 4 and the steps haven’t changed). An Angular application is my end goal and why I made this deviation from the samples.
After the Client application has been created use NuGet to add the IdentityModel package. This package is to make interacting with the Identity Application simpler.
For this first go instead of actually interacting with the Identity Application from Angular I will be using it from MVC instead. The detail of interaction from Angular will come in a later post. The IdentityController is what does the interaction with both the Identity Application and the API Application interactions in this version of the client application. The following is the full IdentityController class.
public async Task<IActionResult> Index() { var discovery = await DiscoveryClient.GetAsync("http://localhost:5000"); var tokenClient = new TokenClient(discovery.TokenEndpoint, "clientApp", "secret"); var tokenResponse = await tokenClient.RequestClientCredentialsAsync("apiApp"); ViewData["tokenResult"] = tokenResponse.IsError ? tokenResponse.Error : tokenResponse.Json.ToString(); var client = new HttpClient(); client.SetBearerToken(tokenResponse.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 above, you can see the IdentityModel in action. Using the DiscoveryClient means the client application only needs to know about the root address of the Identity Application. TokenClient is being used to request a token from the Identity Application for the clientApp using the client secret which in this case is actually the word secret. Keep in mind in a real application secrets should be kept using the ASP.NET Core Secrets manager, see this related post. Also, take note that clientApp and secret are the values that were defined in the Config class of the Identity application.
The rest of the code is taking the token response and making a call to the API application with the response from both of those calls being stored in ViewData for display on the view associated with the controller.
The view is just an Index.cshtml file in the path Views/Identity. The following is the full view.
@{ ViewData["Title"] = "Identity"; } @ViewData["tokenResult"] @ViewData["apiResult"]
It isn’t pretty, but the whole point of this controller and view is just for verification that the three applications are properly communicating with each other.
URL Configuration
In this setup, it is important that the URL for the Identity Application and API Application be fixed so they can be accessed by the client. In a more production level application, these values would at a minimum need to be in configuration. The following is the setup used for this solution.
- Identity Application – http://localhost:5000
- API Application – http://localhost:5001
- Client Application – http://localhost:5002
There are a couple of ways to configure test values. The first is to open the project properties and select the Debug tab and set the App URL.
The second option is to go to the Program class for each project and add a UseUrls to the WebHostBuilder like the following.
public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseUrls("http://localhost:5002") .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .Build(); host.Run(); }
Wrapping up
After going through the above process I now have a much better understanding of how the very basic setup using Identity Server should work. I hope if you made this far you found some helpful bits.
There is a bit of strangeness using Visual Studio to try and launch all three applications and can result in an error message if multiple of the projects are run in debug mode. For the most part, this can be worked around by only debugging one application at a time. It is a bit annoying at the beginning stages, but once an applications gets past that point I imagian that the Identity Application won’t require much debugging.
If there are any questions please leave a comment and I would be happy to try and help. The finished code can be found here. This basic example will be expanded over time and all the related entries can be found in the IdentityServer category.
Identity Server: Sample Exploration and Initial Project Setup Read More »