Implementing OpenID Code Flow with PKCE using OpenIddict and Angular

This article shows how to implement the OpenID Connect Code Flow with PKCE using OpenIddict hosted in an ASP.NET Core application, an ASP.NET Core Web API and an Angular application as the client.


2021-12-24 Updated to ASP.NET Core 6, Angular 13

2020-12-26 Updated to ASP.NET Core 5, Angular 11, OpenIddict 3

2017-05-27 Updated to ASP.NET Core 2.1, Angular 6.0.3

2017-11-24 Updated to ASP.NET Core 2, Angular 5, angular-auth-oidc-client

Three different projects are used to implement the application. The OpenIddict Server with adds the Code Flow with PKCE is used to authenticate and authorise, the resource server is used to provide the API and uses introspection to validate the HTTP requests and the access tokens and the Angular application implements the UI.

OpenIddict Server implementing the Code Flow with PKCE

NuGet package manager is used to download the required packages. You need to download and install the correct OpenIddict packages for the token server.

Or you can just add them directly to the csproj file.

<Project Sdk="Microsoft.NET.Sdk.Web">


    <None Remove="usersdatabase.sqlite" />

    <Content Include="usersdatabase.sqlite">

    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.1">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    <PackageReference Include="OpenIddict" Version="3.1.1" />
    <PackageReference Include="OpenIddict.AspNetCore" Version="3.1.1" />
    <PackageReference Include="OpenIddict.EntityFrameworkCore" Version="3.1.1" />
    <PackageReference Include="OpenIddict.Quartz" Version="3.1.1" />
    <PackageReference Include="OpenIddict.Server.AspNetCore" Version="3.1.1" />
    <PackageReference Include="Quartz.Extensions.Hosting" Version="3.3.3" />
	  <PackageReference Include="BuildBundlerMinifier" Version="3.2.449" />

    <PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
    <PackageReference Include="Serilog" Version="2.10.0" />
    <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
    <PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
    <PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
    <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />

   <Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('node_modules') ">
    <!-- Ensure Node.js is installed -->
    <Exec Command="node --version" ContinueOnError="true">
      <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
    <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from, and then restart your command prompt or IDE." />
    <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
    <Exec WorkingDirectory="" Command="npm install" />

The OpenIddict packages are configured in the ConfigureServices and the Configure methods in the Startup class. The following code configures the OpenID Connect server with a SQLite database using Entity Framework Core. The required endpoints are enabled, and Json Web tokens are used.

public void ConfigureServices(IServiceCollection services)

	services.AddDbContext<ApplicationDbContext>(options =>
		// Configure the context to use Microsoft SQL Server.

		// Register the entity sets needed by OpenIddict.
		// Note: use the generic overload if you need
		// to replace the default OpenIddict entities.

	// Register the Identity services.
	services.AddIdentity<ApplicationUser, IdentityRole>()

	services.AddCors(options =>
			builder =>
	// Configure Identity to use the same JWT claims as OpenIddict instead
	// of the legacy WS-Federation claims it uses by default (ClaimTypes),
	// which saves you from doing the mapping in your authorization controller.
	services.Configure<IdentityOptions>(options =>
		options.ClaimsIdentity.UserNameClaimType = Claims.Name;
		options.ClaimsIdentity.UserIdClaimType = Claims.Subject;
		options.ClaimsIdentity.RoleClaimType = Claims.Role;

	// OpenIddict offers native integration with Quartz.NET to perform scheduled tasks
	// (like pruning orphaned authorizations/tokens from the database) at regular intervals.
	services.AddQuartz(options =>

	// Register the Quartz.NET service and configure it to block shutdown until jobs are complete.
	services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);


		// Register the OpenIddict core components.
		.AddCore(options =>
			// Configure OpenIddict to use the Entity Framework Core stores and models.
			// Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities.

			// Developers who prefer using MongoDB can remove the previous lines
			// and configure OpenIddict to use the specified MongoDB database:
			// options.UseMongoDb()
			//        .UseDatabase(new MongoClient().GetDatabase("openiddict"));

			// Enable Quartz.NET integration.

		// Register the OpenIddict server components.
		.AddServer(options =>
			// Enable the authorization, device, logout, token, userinfo and verification endpoints.

			// Note: this sample uses the code, device code, password and refresh token flows, but you
			// can enable the other flows if you need to support implicit or client credentials.

			// Mark the "email", "profile", "roles" and "dataEventRecords" scopes as supported scopes.
			options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, "dataEventRecords");

			// Register the signing and encryption credentials.

			// Force client applications to use Proof Key for Code Exchange (PKCE).

			// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
				   .DisableTransportSecurityRequirement(); // During development, you can disable the HTTPS requirement.

			// Note: if you don't want to specify a client_id when sending
			// a token or revocation request, uncomment the following line:
			// options.AcceptAnonymousClients();

			// Note: if you want to process authorization and token requests
			// that specify non-registered scopes, uncomment the following line:
			// options.DisableScopeValidation();

			// Note: if you don't want to use permissions, you can disable
			// permission enforcement by uncommenting the following lines:
			// options.IgnoreEndpointPermissions()
			//        .IgnoreGrantTypePermissions()
			//        .IgnoreResponseTypePermissions()
			//        .IgnoreScopePermissions();

			// Note: when issuing access tokens used by third-party APIs
			// you don't own, you can disable access token encryption:
			// options.DisableAccessTokenEncryption();

		// Register the OpenIddict validation components.
		.AddValidation(options =>
			// Configure the audience accepted by this resource server.
			// The value MUST match the audience associated with the
			// "demo_api" scope, which is used by ResourceController.

			// Import the configuration from the local OpenIddict server instance.

			// Register the ASP.NET Core host.

			// For applications that need immediate access token or authorization
			// revocation, the database entry of the received tokens and their
			// associated authorizations can be validated for each API call.
			// Enabling these options may have a negative impact on performance.
			// options.EnableAuthorizationEntryValidation();
			// options.EnableTokenEntryValidation();

	services.AddTransient<IEmailSender, AuthMessageSender>();
	services.AddTransient<ISmsSender, AuthMessageSender>();

	// Register the worker responsible of seeding the database with the sample clients.
	// Note: in a real world application, this step should be part of a setup script.

The Configure method and the authentication middleware.

public void Configure(IApplicationBuilder app)





	app.UseRequestLocalization(options =>
		options.AddSupportedCultures("en-US", "fr-FR");
		options.AddSupportedUICultures("en-US", "fr-FR");


	app.UseEndpoints(options => options.MapControllerRoute(
		name: "default",
		pattern: "{controller=Home}/{action=Index}/{id?}"));

Entity Framework Core database migrations:

dotnet ef migrations add test 
dotnet ef database update test

The UserinfoController controller is used to return user data to the client. The API requires a token which is validated using the JWT Bearer token validation, configured in the Startup class.

The required claims need to be added here, as the application requires. This example adds some extra role claims which are used in the Angular SPA.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using OpeniddictServer.Models;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using static OpenIddict.Abstractions.OpenIddictConstants;

namespace OpeniddictServer.Controllers
    public class UserinfoController : Controller
        private readonly UserManager<ApplicationUser> _userManager;

        public UserinfoController(UserManager<ApplicationUser> userManager)
            => _userManager = userManager;

        [Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
        [HttpGet("~/connect/userinfo"), HttpPost("~/connect/userinfo")]
        [IgnoreAntiforgeryToken, Produces("application/json")]
        public async Task<IActionResult> Userinfo()
            var user = await _userManager.GetUserAsync(User);
            if (user is null)
                return Challenge(
                    authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                    properties: new AuthenticationProperties(new Dictionary<string, string>
                        [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidToken,
                        [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                            "The specified access token is bound to an account that no longer exists."

            var claims = new Dictionary<string, object>(StringComparer.Ordinal)
                // Note: the "sub" claim is a mandatory claim and must be included in the JSON response.
                [Claims.Subject] = await _userManager.GetUserIdAsync(user)

            if (User.HasScope(Scopes.Email))
                claims[Claims.Email] = await _userManager.GetEmailAsync(user);
                claims[Claims.EmailVerified] = await _userManager.IsEmailConfirmedAsync(user);

            if (User.HasScope(Scopes.Phone))
                claims[Claims.PhoneNumber] = await _userManager.GetPhoneNumberAsync(user);
                claims[Claims.PhoneNumberVerified] = await _userManager.IsPhoneNumberConfirmedAsync(user);

            if (User.HasScope(Scopes.Roles))
                claims[Claims.Role] = await _userManager.GetRolesAsync(user);

            // Note: the complete list of standard claims supported by the OpenID Connect specification
            // can be found here:

            return Ok(claims);

The AuthorizationController controller implements the Authorize method where the claims can be added to the tokens as required. The flow in this example requires the authorization code flow..

If you require more examples, or different flows, refer to the excellent openiddict-samples .

Angular Code Flow with PKCE client

The Angular application uses the AuthConfiguration class to set the options required for the OpenID Connect Code Flow. The ‘code’ is defined as the response type so that an access_token is returned as well as the id_token using the OIDC code flow with a PKCE. The jwks_url is required so that the client can ge the signiture from the server to validate the token. The userinfo_url and the logoutEndSession_url are used to define the user data url and the logout url. The configuration here has to match the configuration on the server.

The OidcSecurityService is used to send the login request to the server and also handle the callback which validates the tokens. This class also persists the token data to the local storage.

The OidcSecurityValidation class defines the functions used to validate the tokens defined in the OpenID Connect specification for the Code Flow with PKCE.

jsrsasign is used to validate the token signature.

Once logged into the application, the access_token is added to the header of each request and sent to the resource server or the required APIs on the OpenIddict server.

private setHeaders(): any {
	this.headers = new HttpHeaders();
	this.headers = this.headers.set('Content-Type', 'application/json');
	this.headers = this.headers.set('Accept', 'application/json');

	const token = this.securityService.getAccessToken();
	if (token !== '') {
		const tokenValue = 'Bearer ' + token;
		this.headers = this.headers.append('Authorization', tokenValue);

ASP.NET Core Resource Server API

The resource server provides an API protected by authorization policies

using ResourceServer.Model;
using ResourceServer.Repositories;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace ResourceServer.Controllers
    public class DataEventRecordsController : Controller
        private readonly DataEventRecordRepository _dataEventRecordRepository;

        public DataEventRecordsController(DataEventRecordRepository dataEventRecordRepository)
            _dataEventRecordRepository = dataEventRecordRepository;

        public IActionResult Get()
            return Ok(_dataEventRecordRepository.GetAll());

        public IActionResult Get(long id)
            return Ok(_dataEventRecordRepository.Get(id));

        public void Post([FromBody]DataEventRecord value)

        public void Put(long id, [FromBody]DataEventRecord value)
            _dataEventRecordRepository.Put(id, value);

        public void Delete(long id)

The policies are implemented in the Startup class and the scope dataEventRecords.

services.AddScoped<IAuthorizationHandler, RequireScopeHandler>();

services.AddAuthorization(options =>
	options.AddPolicy("dataEventRecordsPolicy", policyUser =>
		policyUser.Requirements.Add(new RequireScope());

Introspection is used to validate the API HTTP requests.

services.AddAuthentication(options =>
	options.DefaultScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;

// Register the OpenIddict validation components.
	.AddValidation(options =>
		// Note: the validation handler uses OpenID Connect discovery
		// to retrieve the address of the introspection endpoint.

		// Configure the validation handler to use introspection and register the client
		// credentials used when communicating with the remote introspection endpoint.

		// Register the System.Net.Http integration.

		// Register the ASP.NET Core host.

Running the application

When the application is started, all 3 applications are run, using the Visual Studio multiple project start option.

After the user clicks the login button, the user is redirected to the OpenIddict server to login.

After a successful login, the user is redirected back to the Angular application.



  1. Hi,

    I have implemented your code, but while testing am getting the following error in my angular app.

    Unexpected token ( in JSON at position 1.

    This error comes when AuthorizeCallback tries to decode the base64 for the token.


    1. Hi I have done a large update, you need to build the client app as well.

      npm install
      npm run build-production

      Greetings Damien

      1. I’m getting the same error when trying to use the latest version. It looks like it’s trying to parse the id_token as json, but the decoded version is not json.

  2. Extremely helpful, thank you!

    I’m adapting to use a SQL db, (I use db first so I’m working with the sql tables). I’ve gotten the client_id and redirect uri’s correct, but am at the stage when I click “login” I’m getting:

    error_description:Confidential clients are not allowed to retrieve an access token from the authorization endpoint.

    Can you point me in the right direction?


  3. Got it, it was the “Type” field of table OpenIddictApplications (was set to confidential, changed to public).

  4. All I get is “This is server routing, not angular routing”

    1. Hi Alfetta159, In the startup class you need to add the angular route so it won’t be handled by the server routing.

      Greetings Damien

      1. Daniel R. Przybylski · ·

        What is the point of not having it added?

        And how do I add it?

    2. You might want to use server routing for files, or MVC or whatever

      1. I’ve just updated code to OpenIddict 2.0.0-rc2-0792 and have successfully integrated custom OpenIddict table names using db first dotnet ef scaffolding. Server app coming up fine, angular app returning as above (“This is server routing…”). I’m sure this reflects my lack of familiarity of working with an angular app in a Visual Studio 2017 solution/project. Anything you can add to clarify the solution would be greatly appreciated

      2. In the startup class you need to add all the client routes, so that the MVC server app does n0t try to route it.

  5. […] There are some good examples of how to do this on the web, such as this article by Kévin Chalet or this one by Damien Bowden. Additionally, there are the ASP.NET Core/JavaScript samples for OpenIddict […]

  6. Great article.
    Can this code be updated to Core 2.0 and Angular 5 with latest openiddict please?

    1. yes, as soon as possible

    2. Many Thanks
      Will wait for it.

  7. Any plans soon to migrate to Core 2 and Angular 5?

  8. Updated ASP.NET Core 2, Angular 5, angular-auth-oidc-client

  9. Thanks a lot. This is exactly what was required. I have been a keen follower of your blogs.

  10. I tested the latest code.
    The authentication part is working OK.
    But while accessing resource server, it is throwing 404 error.
    Any help?

  11. The latest update works perfectly.
    Thanks a lot.
    A great article.

  12. I pulled your latest rep from github, and when I startup the project in vs2017, I an error “This site can’t be reached
    The webpage at https://localhost:44319/ might be temporarily down or it may have moved permanently to a new web address.”

    1. hi Clint, you might need to do a npm install, then a npm run build-dev, greetings Damien

  13. Peter Griffin · · Reply

    I have all the components running – auth server, resource server and the client. I was able to register a user via the UI, I can see the user in the DB. However when I try to log in (with correct credentials, I’m sure of it), I get a 400 Bad Request response from the auth server with the following message:

    error_description:The specified ‘redirect_uri’ parameter is not valid for this client application.

    VS has assigned a different port number to my resource server, but I’ve updated this everywhere in the code.

    1. Peter Griffin · · Reply

      I somehow got around the invalid_request by downloading the solution again, setting the resource server port to 44308 in the project properties, as this is what’s in the source files. I ran those servers. I changed the client back so that redirect port would be 44308. Now when I log in the auth server redirects me to https://localhost:44308 where I get a 404. No Angular app pops up, nothing happens. When I try to manually navigate to the urls of the controllers, I get a 401. Nothing about this solution works.

  14. Hi Peter, the github examples should work without changes, only the certs need to be excepted with the system running it. I run with IIS Express. I will check this again to make sure.

    Greetings Damien

    1. Peter Griffin · · Reply

      Hi Damien, sorry for the harsh words, I’m getting a bit frustrated here. Currently what happens when I log in is I get redirected to this url

      and there I get a 404. I know the resource server is running, because this url https://localhost:44308/index.html returns the index page.

      1. So the login to the STS is finished. The return URL is not been handled by the client application, maybe server routing first. You need that the client routing handles the return URL. Disable the server routing for this return URL. Note it works when you open the index static page because this is not handled by the server routing.

        Greetings Damien

  15. Peter Griffin · · Reply

    Thanks, I’ll try that. Did you get a chance to check your sample?

    1. I have updated the example, and tested it. seems to work

      1. Peter Griffin · ·

        Thanks. I’ve been sitting here for the past 3 hours just trying to get the projects to run. First I had to update my VS, as apparently it was old. Then I had to update then .NET SDK, then the .NET Core SDK. Then the ASPNET core app failed while starting, the ASPNET core web server output said it was unable to locate the correct aspnet core app version. It asked for 2.1.0-rc1, so I installed that one specifically. Now I get a compile time error:

        Error The project was restored using Microsoft.NETCore.App version 2.1.0-preview2-26406-04, but with current settings, version 2.1.0-rc1 would be used instead. To resolve this issue, make sure the same settings are used for restore and for subsequent operations such as build or publish. Typically this issue can occur if the RuntimeIdentifier property is set during build or publish but not during restore.

        Could you possibly tell me what to install/uninstall or which setting to change? Cause I’m at the end of my wits and just about ready to take out a hammer and smash my machine to bits.

      2. Peter Griffin · ·

        Nope, after spending several hours trying to get this updated version to run, I got to the exact same situation I had last time: auth server running, resource server running, client running. I am able to register a user, log in, and then am redirected to the resource server where I get a 404.
        I think it’s time for me to declare defeat and stop spending my time on this sample. I was really happy when I first saw it, cause I thought I could get my little side project started, without having to go through the pain of having to code the authentication infrastructure myself. But it keeps not working no matter what I do and I’m at the end of my mental capacity to continue trying.
        So long and thanks for all the help.

      3. Hi Peter

        Sorry to hear that 😦 Hope you have success building the new solution.

        Greetings Damien

  16. […] Implementing OpenID Implicit Flow using OpenIddict and Angular by Damien Bowden. […]

  17. Ivan-Mark Debono · · Reply

    Hi Damien,

    I created a solution based on your code. I’m not using Angular but instead added a test Console app and also added ClientCredentialsFlow based off the original Aridka sample. My resource server project is an empty project with the default WeatherForecast stuff.

    I do get a token, however, I get 401 Unauthorized when I access the api.

    Any ideas what I could be doing wrong?

    1. Hi Ivan-Mark

      The access token you send to the API has different claims to the ones expected in the API. Open the access token using or and check the claims against the ones validated in the API. You might need to turn of the decyption in the token to check like this. Openiddict has a seetings for this, I think.

      Or you could add debugging, logging to the API with PII and check the message why the token was rejected.

      Greetings Damien

  18. Hi Damien,

    I want to use Openiddict with a react native app(client) authentication API(outside protected resource to make sure the user is valid) and a resource API to pull data from endpoints.

    They need to be seperate from the Openiddict project. Is my best bet to register them all as clients to that Openiddict server and give scope permissions to each one? What about introspection? Is that applicable in my situation?


  19. […] Implementing OpenID Code Flow with PKCE using OpenIddict and Angular […]

  20. Hello all, I need angular 13 and core web api 6 version can any one has link give me. thanks

  21. Ivan Debono · · Reply

    Is it possible to expand the article and code on how the Openiddict authserver handles logout and revocation.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: