When setting up a new instance of IdentityServer3 some things that you do, you do for every project.
This will be a small tutorial / series following my steps when setting up a simple start kit for IdentityServer3 that also contains IdentityManager for users and admin for clients.
This is to have as a starting point to quickly get started with IdentityServer when setting up a new projects. This can also be used as a guide for getting started and setting up IdentityServer 3 from scratch and as a guide to understanding all the lose parts.
Setting up the project
We’ll start by creating an empty ASP.NET Web Application, that will be the host application for the project.

Since we don’t need any default template for the project we can choose to create an Empty project.

Next up, is setting up a development environment using https. For this, we need to open the properties for the project and set the Web -> Project url to an https:// url

Remember to click the Create Virtual Directory button to enable the url.

To be able to handle the built in assets and more we need to enable a setting in the Web.config
file.
1<configuration>2 ...3 <system.webServer>4 <modules runAllManagedModulesForAllRequests="true" />5 </system.webServer>6</configuration>
If when you start your IdentityServer you encounter a welcome page without any valid css, js or images you’ve probably missed the “RunAllManagedModulesForAllRequests” setting.
Now we are ready to continue and start installing packages.
Setting up IdentityServer 3 core features
Installing basic packages
After we are done with project setup we’ll install a few packages, IdentityServer3 to install the base for IdentityServer, IdentityServer3.AspNetIdentity to give support for ASP.NET Identity, IdentityServer3.EntityFramework to enable to have persistent data from an SQL Server using EntityFramework, Microsoft.AspNet.Identity.EntityFramework to create the ASP.NET Identity Entities and Microsoft.Owin to be able to setup IdentityServer at startup
1Install-Package IdentityServer32Install-Package IdentityServer3.AspNetIdentity3Install-Package IdentityServer3.EntityFramework4Install-Package Microsoft.Owin.Host.SystemWeb5Install-Package Microsoft.AspNet.Identity.EntityFramework
If you want the server to be self-hosted you can create an console application and install the packages Microsoft.Owin.Host.HttpListner and Microsoft.Owin.Hosting instead of Microsoft.Owin.Host.SystemWeb
Setting up some prerequisites
Constants
First we are going to setup Constants.cs, a file that will be used to contain some constants through the process. Here we define a name for the connection string that will be used across the starter kit and also the core endpoint for IdentityServer.
1// <copyright file="Constants.cs">2// 2017 - Johan Boström3// </copyright>45namespace IdentityServer3.StarterKit6{7 public static class Constants8 {9 public const string ConnectionStringName = "AspId";1011 public class Routes12 {13 public const string Core = "/ids";14 public const string IdMgr = "/idm";15 public const string IdAdm = "/ida";16 }17 }18}
Certificate
We also need to have a certificate that we can use to sign tokens etc. For test-purposes I’ve downloaded the test certificate from IdentityServers github, that can be found here. The certificate is then added to the project and the build action is changed to Embedded Resource.
Now we need to be able to read the certificate and this can be done by creating the class certificate and reading the file as a stream and then creating a X509Certificate2.
1// <copyright file="Certificate.cs">2// 2017 - Johan Boström3// </copyright>45using System.IO;6using System.Security.Cryptography.X509Certificates;78namespace IdentityServer3.StarterKit.Config9{10 public static class Certificate11 {12 public static X509Certificate2 Get()13 {14 var assembly = typeof(Certificate).Assembly;15 using (var stream = assembly.GetManifestResourceStream("IdentityServer3.StarterKit.Config.idsrv3test.pfx"))16 // Should be the path to the embeded certificate17 {18 return new X509Certificate2(ReadStream(stream), "idsrv3test");19 }20 }2122 private static byte[] ReadStream(Stream input)23 {24 var buffer = new byte[16 * 1024];25 using (var ms = new MemoryStream())26 {27 int read;28 while ((read = input.Read(buffer, 0, buffer.Length)) > 0)29 ms.Write(buffer, 0, read);30 return ms.ToArray();31 }32 }33 }34}
Database models
Now we can start setting up models to use for the user when storing data to the databases, this model can be used to keep extra information on the entries. On the user we will add a first name and a last name to be able to enter later. Other than that there are no need for more info right now since the model will inherit all it needs from IdentityUser.
1// <copyright file="User.cs">2// 2017 - Johan Boström3// </copyright>45using Microsoft.AspNet.Identity.EntityFramework;67namespace IdentityServer3.StarterKit.Models8{9 public class User : IdentityUser10 {11 public string FirstName { get; set; }12 public string LastName { get; set; }13 }14}
Context
Next up is to setup the database context, this is done by creating a class and inheriting from IdentityDbContext and use the user model that we created together with some models that come directly from Microsoft.AspNet.Identity.EntityFramework.
1// <copyright file="Context.cs">2// 2017 - Johan Boström3// </copyright>45using IdentityServer3.StarterKit.Models;6using Microsoft.AspNet.Identity.EntityFramework;78namespace IdentityServer3.StarterKit.Db9{10 public class Context : IdentityDbContext<User, IdentityRole, string,IdentityUserLogin, IdentityUserRole, IdentityUserClaim>11 {12 public Context(string connString)13 : base(connString)14 {15 }16 }17}
Stores
Now that we have the context in place we can create a UserStore and RoleStore that the system can use for creating, finding, updating and deleting users and stores. We will create them by inheriting from the stores located in Microsoft.AspNet.Identity.EntityFramework.
1// <copyright file="UserStore.cs">2// 2017 - Johan Boström3// </copyright>45using IdentityServer3.StarterKit.Db;6using IdentityServer3.StarterKit.Models;7using Microsoft.AspNet.Identity.EntityFramework;89namespace IdentityServer3.StarterKit.Stores10{11 public class UserStore : UserStore<User, IdentityRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>12 {13 public UserStore(Context context)14 : base(context)15 {16 }17 }18}
1// <copyright file="RoleStore.cs">2// 2017 - Johan Boström3// </copyright>45using IdentityServer3.StarterKit.Db;6using Microsoft.AspNet.Identity.EntityFramework;78namespace IdentityServer3.StarterKit.Stores9{10 public class RoleStore : RoleStore<IdentityRole>11 {12 public RoleStore(Context context)13 : base(context)14 {15 }16 }17}
Managers
1// <copyright file="UserManager.cs">2// 2017 - Johan Boström3// </copyright>45using IdentityServer3.StarterKit.Factories;6using IdentityServer3.StarterKit.Models;7using IdentityServer3.StarterKit.Stores;8using Microsoft.AspNet.Identity;910namespace IdentityServer3.StarterKit.Managers11{12 public class UserManager : UserManager<User, string>13 {14 public UserManager(UserStore store)15 : base(store)16 {17 ClaimsIdentityFactory = new ClaimsFactory();18 }19 }20}
1// <copyright file="RoleManager.cs">2// 2017 - Johan Boström3// </copyright>45using IdentityServer3.StarterKit.Stores;6using Microsoft.AspNet.Identity;7using Microsoft.AspNet.Identity.EntityFramework;89namespace IdentityServer3.StarterKit.Managers10{11 public class RoleManager : RoleManager<IdentityRole>12 {13 public RoleManager(RoleStore store)14 : base(store)15 {16 }17 }18}
Creating the ClaimsIdentity
AspNet Identity uses the ClaimsIdentity as the identity type, and for us to be able to create an instance of this from the user model we created before we need to setup a ClaimsIdentityFactory. With this we will be be able to extract the first- and last name that we added to the user model, and add these as possible claims.
1// <copyright file="ClaimsIdentityFactory.cs">2// 2017 - Johan Boström3// </copyright>45using System.Security.Claims;6using System.Threading.Tasks;7using IdentityServer3.StarterKit.Models;8using Microsoft.AspNet.Identity;910namespace IdentityServer3.StarterKit.Factories11{12 public class ClaimsIdentityFactory : ClaimsIdentityFactory<User, string>13 {14 public ClaimsIdentityFactory()15 {16 UserIdClaimType = Core.Constants.ClaimTypes.Subject;17 UserNameClaimType = Core.Constants.ClaimTypes.PreferredUserName;18 RoleClaimType = Core.Constants.ClaimTypes.Role;19 }2021 public override async Task<ClaimsIdentity> CreateAsync(UserManager<User, string> manager, User user, string authenticationType)22 {23 var ci = await base.CreateAsync(manager, user, authenticationType);2425 if (!string.IsNullOrWhiteSpace(user.FirstName))26 ci.AddClaim(new Claim("given_name", user.FirstName));2728 if (!string.IsNullOrWhiteSpace(user.LastName))29 ci.AddClaim(new Claim("family_name", user.LastName));3031 return ci;32 }33 }34}
User service
Now we just need an implementation of AspNetIdentityUserService that is part of the IdentityServer3.AspNetIdentity package that we installed before, so that it will use out new user model. And so that we can start putting all the lose parts together.
1// <copyright file="UserService.cs">2// 2017 - Johan Boström3// </copyright>45using IdentityServer3.AspNetIdentity;6using IdentityServer3.StarterKit.Managers;7using IdentityServer3.StarterKit.Models;89namespace IdentityServer3.StarterKit.Services10{11 public class UserService : AspNetIdentityUserService<User, string>12 {13 public UserService(UserManager userManager)14 : base(userManager)15 {16 }17 }18}
Configuring IdentityServer
Setting up default scopes, users and clients
To get started we can setup some values that can be used as a starting point for the data that we can manipulate and add from. Theses values below will only be added if there are no other entities in the tables. For the scopes I decided to add the default scopes to the database, I added a user so that we have a user to login with and try the system. I also added a client that we can use to build from in later parts of the start kit series.
1// <copyright file="DefaultSetup.cs">2// 2017 - Johan Boström3// </copyright>45using System.Collections.Generic;6using System.Linq;7using System.Security.Claims;8using IdentityServer3.Core.Models;9using IdentityServer3.EntityFramework;10using IdentityServer3.StarterKit.Db;11using IdentityServer3.StarterKit.Managers;12using IdentityServer3.StarterKit.Models;13using IdentityServer3.StarterKit.Stores;14using Microsoft.AspNet.Identity;1516namespace IdentityServer3.StarterKit.Config17{18 public class DefaultSetup19 {20 public static void Configure(EntityFrameworkServiceOptions options)21 {22 using (var db = new ScopeConfigurationDbContext(options.ConnectionString, options.Schema))23 {24 if (!db.Scopes.Any())25 {26 foreach (var s in StandardScopes.All)27 {28 var e = s.ToEntity();29 db.Scopes.Add(e);30 }3132 foreach (var s in StandardScopes.AllAlwaysInclude)33 {34 var e = s.ToEntity();35 db.Scopes.Add(e);36 }3738 db.SaveChanges();39 }40 }4142 using (var db = new Context(options.ConnectionString))43 {44 if (!db.Users.Any())45 {46 using (var userManager = new UserManager(new UserStore(db)))47 {48 var defaultUserPassword = "skywalker"; // Must be atleast 6 characters49 var user = new User50 {51 UserName = "administrator",52 FirstName = "Luke",53 LastName = "Skywalker",54 Email = "[email protected]",55 EmailConfirmed = true56 };57 userManager.Create(user, defaultUserPassword);58 userManager.AddClaim(user.Id,59 new Claim(Core.Constants.ClaimTypes.WebSite, "https://www.johanbostrom.se/"));60 }6162 db.SaveChanges();63 }64 }6566 using (var db = new ClientConfigurationDbContext(options.ConnectionString, options.Schema))67 {68 if (!db.Clients.Any())69 {70 var defaultHybridClient = new Client71 {72 ClientName = "Default Hybrid Client",73 ClientId = "default.hybrid",74 Flow = Flows.Hybrid,75 ClientSecrets = new List<Secret>76 {77 new Secret("default.hybrid.password".Sha256())78 },79 AllowedScopes = new List<string>80 {81 Core.Constants.StandardScopes.OpenId,82 Core.Constants.StandardScopes.Profile,83 Core.Constants.StandardScopes.Email,84 Core.Constants.StandardScopes.Roles,85 Core.Constants.StandardScopes.Address,86 Core.Constants.StandardScopes.Phone,87 Core.Constants.StandardScopes.OfflineAccess88 },89 ClientUri = "https://localhost:44300/",90 RequireConsent = false,91 AccessTokenType = AccessTokenType.Reference,92 RedirectUris = new List<string>(),93 PostLogoutRedirectUris = new List<string>94 {95 "https://localhost:44300/"96 },97 LogoutSessionRequired = true98 };99100 db.Clients.Add(defaultHybridClient.ToEntity());101 db.SaveChanges();102 }103 }104 }105 }106}
Mapping, registration and configuration
Now all that is left is putting everything together and mapping the identityserver core. I decided to create an extensions for IAppBuilder that takes a certificate. Here we configure EntityFramework (EF) from the constants we setup, create a IdentityServerServiceFactory and register services based on the EF configurations. We also register the UserService service we created and run our default configuration setup. Finally it runs the UseIdentityServer extension with the factory and some debugging parameters.
1// <copyright file="AppBuilderExtensions.cs">2// 2017 - Johan Boström3// </copyright>45using System.Security.Cryptography.X509Certificates;6using IdentityServer3.Core.Configuration;7using IdentityServer3.Core.Services;8using IdentityServer3.EntityFramework;9using IdentityServer3.StarterKit.Config;10using IdentityServer3.StarterKit.Db;11using IdentityServer3.StarterKit.Managers;12using IdentityServer3.StarterKit.Services;13using IdentityServer3.StarterKit.Stores;14using Owin;1516namespace IdentityServer3.StarterKit.Extensions17{18 public static class AppBuilderExtensions19 {20 public static IAppBuilder MapCore(this IAppBuilder app, X509Certificate2 signingCertificate)21 {22 app.Map(Constants.Routes.Core, coreApp =>23 {24 var efConfig = new EntityFrameworkServiceOptions25 {26 ConnectionString = Constants.ConnectionStringName27 };2829 var factory = new IdentityServerServiceFactory();3031 factory.RegisterConfigurationServices(efConfig);32 factory.RegisterOperationalServices(efConfig);33 factory.RegisterClientStore(efConfig);34 factory.RegisterScopeStore(efConfig);3536 factory.Register(new Registration<UserManager>());37 factory.Register(new Registration<UserStore>());38 factory.Register(new Registration<Context>(resolver => new Context(Constants.ConnectionStringName)));3940 factory.UserService = new Registration<IUserService, UserService>();4142 DefaultSetup.Configure(efConfig);4344 coreApp.UseIdentityServer(new IdentityServerOptions45 {46 Factory = factory,47 SigningCertificate = signingCertificate,48 SiteName = "IdentityServer3 Starter Kit",49 LoggingOptions = new LoggingOptions50 {51 EnableKatanaLogging = true52 },53 EventsOptions = new EventsOptions54 {55 RaiseFailureEvents = true,56 RaiseInformationEvents = true,57 RaiseSuccessEvents = true,58 RaiseErrorEvents = true59 }60 });61 });6263 return app;64 }65 }66}
Adding the last bit of the puzzle, the Owin startup. I created a file called Startup.cs and just ran the core mapping extension for the IAppBuilder.
1// <copyright file="Startup.cs">2// 2017 - Johan Boström3// </copyright>45using IdentityServer3.StarterKit;6using IdentityServer3.StarterKit.Config;7using IdentityServer3.StarterKit.Extensions;8using Microsoft.Owin;9using Owin;1011[assembly: OwinStartup(typeof(Startup))]1213namespace IdentityServer3.StarterKit14{15 public class Startup16 {17 public void Configuration(IAppBuilder app)18 {19 var certificate = Certificate.Get();20 app.MapCore(certificate);21 }22 }23}
Done!
Well, finally everything should be done. When running we should now be able to go to https://localhost:44300/ids and see the welcome screen. We should also be able to click the link to the permission page and login with our administrator user.
All the code examples and the complete starter kit project can be found over at my GitHub page.