Browse Source

Make the OpenIddict client stack stateful by default and introduce OpenIddict.Client.DataProtection

pull/1429/head
Kévin Chalet 4 years ago
parent
commit
5758c4a6d9
  1. 7
      OpenIddict.sln
  2. 23
      sandbox/OpenIddict.Sandbox.AspNet.Client/Models/ApplicationDbContext.cs
  3. 2
      sandbox/OpenIddict.Sandbox.AspNet.Client/OpenIddict.Sandbox.AspNet.Client.csproj
  4. 52
      sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs
  5. 40
      sandbox/OpenIddict.Sandbox.AspNet.Client/Web.config
  6. 9
      sandbox/OpenIddict.Sandbox.AspNet.Server/Web.config
  7. 20
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/Models/ApplicationDbContext.cs
  8. 8
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/OpenIddict.Sandbox.AspNetCore.Client.csproj
  9. 84
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs
  10. 21
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/Worker.cs
  11. 4
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/appsettings.json
  12. 24
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/web.config
  13. 4
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Models/ApplicationDbContext.cs
  14. 1
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/OpenIddict.Sandbox.AspNetCore.Server.csproj
  15. 2
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/appsettings.json
  16. 24
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/web.config
  17. 35
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  18. 1
      src/OpenIddict.AspNetCore/OpenIddict.AspNetCore.csproj
  19. 15
      src/OpenIddict.Client.DataProtection/IOpenIddictClientDataProtectionFormatter.cs
  20. 38
      src/OpenIddict.Client.DataProtection/OpenIddict.Client.DataProtection.csproj
  21. 99
      src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionBuilder.cs
  22. 53
      src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionConfiguration.cs
  23. 48
      src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionConstants.cs
  24. 74
      src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionExtensions.cs
  25. 383
      src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionFormatter.cs
  26. 201
      src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.Protection.cs
  27. 17
      src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.cs
  28. 36
      src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionOptions.cs
  29. 10
      src/OpenIddict.Client/OpenIddictClientBuilder.cs
  30. 12
      src/OpenIddict.Client/OpenIddictClientEvents.Protection.cs
  31. 3
      src/OpenIddict.Client/OpenIddictClientExtensions.cs
  32. 48
      src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs
  33. 323
      src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs
  34. 59
      src/OpenIddict.Client/OpenIddictClientHandlers.cs
  35. 8
      src/OpenIddict.Client/OpenIddictClientOptions.cs
  36. 10
      src/OpenIddict.Client/OpenIddictClientService.cs
  37. 1
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs
  38. 88
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs
  39. 1
      src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConstants.cs
  40. 28
      src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs

7
OpenIddict.sln

@ -124,6 +124,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Sandbox.AspNet.S
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Sandbox.AspNet.Client", "sandbox\OpenIddict.Sandbox.AspNet.Client\OpenIddict.Sandbox.AspNet.Client.csproj", "{BFF5B862-D5FB-4019-8647-C43E5E7EF97D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Client.DataProtection", "src\OpenIddict.Client.DataProtection\OpenIddict.Client.DataProtection.csproj", "{E4D77737-4C73-4520-99E8-8A9E586C69A1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -294,6 +296,10 @@ Global
{BFF5B862-D5FB-4019-8647-C43E5E7EF97D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BFF5B862-D5FB-4019-8647-C43E5E7EF97D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BFF5B862-D5FB-4019-8647-C43E5E7EF97D}.Release|Any CPU.Build.0 = Release|Any CPU
{E4D77737-4C73-4520-99E8-8A9E586C69A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E4D77737-4C73-4520-99E8-8A9E586C69A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E4D77737-4C73-4520-99E8-8A9E586C69A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E4D77737-4C73-4520-99E8-8A9E586C69A1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -340,6 +346,7 @@ Global
{3385BC80-7EBF-4581-8FC8-E18E05EECFA2} = {D544447C-D701-46BB-9A5B-C76C612A596B}
{0DFA4EC2-035A-46D3-AAF6-4BF1DFBC1040} = {F47D1283-0EE9-4728-8026-58405C29B786}
{BFF5B862-D5FB-4019-8647-C43E5E7EF97D} = {F47D1283-0EE9-4728-8026-58405C29B786}
{E4D77737-4C73-4520-99E8-8A9E586C69A1} = {D544447C-D701-46BB-9A5B-C76C612A596B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A710059F-0466-4D48-9B3A-0EF4F840B616}

23
sandbox/OpenIddict.Sandbox.AspNet.Client/Models/ApplicationDbContext.cs

@ -0,0 +1,23 @@
using System.Data.Entity;
namespace OpenIddict.Sandbox.AspNetCore.Server.Models
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.UseOpenIddict();
base.OnModelCreating(modelBuilder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
}

2
sandbox/OpenIddict.Sandbox.AspNet.Client/OpenIddict.Sandbox.AspNet.Client.csproj

@ -17,6 +17,8 @@
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Client.Owin\OpenIddict.Client.Owin.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Client.SystemNetHttp\OpenIddict.Client.SystemNetHttp.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.EntityFramework\OpenIddict.EntityFramework.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Quartz\OpenIddict.Quartz.csproj" />
</ItemGroup>
<ItemGroup>

52
sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs

@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Owin.Security.Cookies;
using OpenIddict.Client;
using OpenIddict.Client.Owin;
using OpenIddict.Sandbox.AspNetCore.Server.Models;
using Owin;
using static OpenIddict.Abstractions.OpenIddictConstants;
@ -33,6 +34,13 @@ namespace OpenIddict.Sandbox.AspNet.Client
// Configure ASP.NET MVC 5.2 to use Autofac when activating controller instances.
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
// Create the database used by the OpenIddict client stack to store tokens.
// Note: in a real world application, this step should be part of a setup script.
using var scope = container.BeginLifetimeScope();
var context = scope.Resolve<ApplicationDbContext>();
context.Database.CreateIfNotExists();
}
private static IContainer CreateContainer()
@ -40,33 +48,49 @@ namespace OpenIddict.Sandbox.AspNet.Client
var services = new ServiceCollection();
services.AddOpenIddict()
.AddClient(options =>
// Register the OpenIddict core components.
.AddCore(options =>
{
// Add a client registration matching the client application definition in the server project.
options.AddRegistration(new OpenIddictClientRegistration
{
Issuer = new Uri("https://localhost:44349/", UriKind.Absolute),
// Configure OpenIddict to use the Entity Framework 6.x stores and models.
// Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities.
options.UseEntityFramework()
.UseDbContext<ApplicationDbContext>();
ClientId = "mvc",
ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654",
RedirectUri = new Uri("https://localhost:44378/signin-oidc", UriKind.Absolute),
Scopes = { Scopes.Email, Scopes.Profile, Scopes.OfflineAccess, "demo_api" }
});
// 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"));
})
// Register the OpenIddict client components.
.AddClient(options =>
{
// Enable the redirection endpoint needed to handle the callback stage.
options.SetRedirectionEndpointUris("/signin-oidc");
// Register the OWIN host and configure the OWIN-specific options.
options.UseOwin()
.EnableRedirectionEndpointPassthrough();
// Register the signing and encryption credentials used to protect
// sensitive data like the state tokens produced by OpenIddict.
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
// Register the OWIN host and configure the OWIN-specific options.
options.UseOwin()
.EnableRedirectionEndpointPassthrough();
// Register the System.Net.Http integration.
options.UseSystemNetHttp();
// Add a client registration matching the client application definition in the server project.
options.AddRegistration(new OpenIddictClientRegistration
{
Issuer = new Uri("https://localhost:44349/", UriKind.Absolute),
ClientId = "mvc",
ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654",
RedirectUri = new Uri("https://localhost:44378/signin-oidc", UriKind.Absolute),
Scopes = { Scopes.Email, Scopes.Profile, Scopes.OfflineAccess, "demo_api" }
});
});
// Create a new Autofac container and import the OpenIddict services.

40
sandbox/OpenIddict.Sandbox.AspNet.Client/Web.config

@ -4,6 +4,13 @@
https://go.microsoft.com/fwlink/?LinkId=301880
-->
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<connectionStrings>
<add name="DefaultConnection" connectionString="Server=(localdb)\mssqllocaldb;Database=openiddict-sandbox-aspnet-client;Trusted_Connection=True;MultipleActiveResultSets=true" providerName="System.Data.SqlClient" />
</connectionStrings>
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />
<add key="webpages:Enabled" value="false" />
@ -12,15 +19,8 @@
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.8" />
<httpRuntime targetFramework="4.8" maxQueryStringLength="32768" />
<httpRuntime targetFramework="4.8" />
</system.web>
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxQueryString="32768" />
</requestFiltering>
</security>
</system.webServer>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
@ -106,6 +106,30 @@
<bindingRedirect oldVersion="0.0.0.0-1.6.5135.21930" newVersion="1.6.5135.21930" />
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Extensions.Caching.Memory" publicKeyToken="adb9793829ddae60" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.1.23.0" newVersion="2.1.23.0" />
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.2.5.0" newVersion="1.2.5.0" />
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.ComponentModel.Annotations" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.1.0" newVersion="4.2.1.0" />
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<system.codedom>
<compilers>

9
sandbox/OpenIddict.Sandbox.AspNet.Server/Web.config

@ -9,7 +9,7 @@
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<connectionStrings>
<add name="DefaultConnection" connectionString="Server=(localdb)\mssqllocaldb;Database=openiddict-aspnet-sandbox;Trusted_Connection=True;MultipleActiveResultSets=true" providerName="System.Data.SqlClient" />
<add name="DefaultConnection" connectionString="Server=(localdb)\mssqllocaldb;Database=openiddict-sandbox-aspnet-server;Trusted_Connection=True;MultipleActiveResultSets=true" providerName="System.Data.SqlClient" />
</connectionStrings>
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />
@ -20,17 +20,12 @@
<system.web>
<authentication mode="None" />
<compilation debug="true" targetFramework="4.8" />
<httpRuntime targetFramework="4.8" maxQueryStringLength="32768" />
<httpRuntime targetFramework="4.8" />
</system.web>
<system.webServer>
<modules>
<remove name="FormsAuthentication" />
</modules>
<security>
<requestFiltering>
<requestLimits maxQueryString="32768" />
</requestFiltering>
</security>
</system.webServer>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

20
sandbox/OpenIddict.Sandbox.AspNetCore.Client/Models/ApplicationDbContext.cs

@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore;
namespace OpenIddict.Sandbox.AspNetCore.Client.Models;
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(modelBuilder);
}
}

8
sandbox/OpenIddict.Sandbox.AspNetCore.Client/OpenIddict.Sandbox.AspNetCore.Client.csproj

@ -8,7 +8,15 @@
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.Client.AspNetCore\OpenIddict.Client.AspNetCore.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Client.DataProtection\OpenIddict.Client.DataProtection.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Client.SystemNetHttp\OpenIddict.Client.SystemNetHttp.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.EntityFrameworkCore\OpenIddict.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Quartz\OpenIddict.Quartz.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
<PackageReference Include="Quartz.Extensions.Hosting" />
</ItemGroup>
</Project>

84
sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs

@ -1,13 +1,32 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.EntityFrameworkCore;
using OpenIddict.Client;
using OpenIddict.Sandbox.AspNetCore.Client.Models;
using Quartz;
using static OpenIddict.Abstractions.OpenIddictConstants;
namespace OpenIddict.Sandbox.AspNetCore.Client;
public class Startup
{
public Startup(IConfiguration configuration)
=> Configuration = configuration;
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
{
// Configure the context to use Microsoft SQL Server.
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
// Register the entity sets needed by OpenIddict.
// Note: use the generic overload if you need
// to replace the default OpenIddict entities.
options.UseOpenIddict();
});
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
@ -21,40 +40,75 @@ public class Startup
options.SlidingExpiration = false;
});
// OpenIddict offers native integration with Quartz.NET to perform scheduled tasks
// (like pruning orphaned authorizations from the database) at regular intervals.
services.AddQuartz(options =>
{
options.UseMicrosoftDependencyInjectionJobFactory();
options.UseSimpleTypeLoader();
options.UseInMemoryStore();
});
// Register the Quartz.NET service and configure it to block shutdown until jobs are complete.
services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);
services.AddOpenIddict()
.AddClient(options =>
// Register the OpenIddict core components.
.AddCore(options =>
{
// Add a client registration matching the client application definition in the server project.
options.AddRegistration(new OpenIddictClientRegistration
{
Issuer = new Uri("https://localhost:44395/", UriKind.Absolute),
// Configure OpenIddict to use the Entity Framework Core stores and models.
// Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities.
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
ClientId = "mvc",
ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654",
RedirectUri = new Uri("https://localhost:44381/signin-oidc", UriKind.Absolute),
Scopes = { Scopes.Email, Scopes.Profile, Scopes.OfflineAccess, "demo_api" }
});
// 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.
options.UseQuartz();
})
// Register the OpenIddict client components.
.AddClient(options =>
{
// Enable the redirection endpoint needed to handle the callback stage.
options.SetRedirectionEndpointUris("/signin-oidc");
// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
options.UseAspNetCore()
.EnableStatusCodePagesIntegration()
.EnableRedirectionEndpointPassthrough();
// Register the signing and encryption credentials used to protect
// sensitive data like the state tokens produced by OpenIddict.
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
options.UseAspNetCore()
.EnableStatusCodePagesIntegration()
.EnableRedirectionEndpointPassthrough();
// Register the System.Net.Http integration.
options.UseSystemNetHttp();
// Add a client registration matching the client application definition in the server project.
options.AddRegistration(new OpenIddictClientRegistration
{
Issuer = new Uri("https://localhost:44395/", UriKind.Absolute),
ClientId = "mvc",
ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654",
RedirectUri = new Uri("https://localhost:44381/signin-oidc", UriKind.Absolute),
Scopes = { Scopes.Email, Scopes.Profile, Scopes.OfflineAccess, "demo_api" }
});
});
services.AddHttpClient();
services.AddControllersWithViews();
// Register the worker responsible for creating the database used to store tokens.
// Note: in a real world application, this step should be part of a setup script.
services.AddHostedService<Worker>();
}
public void Configure(IApplicationBuilder app)

21
sandbox/OpenIddict.Sandbox.AspNetCore.Client/Worker.cs

@ -0,0 +1,21 @@
using OpenIddict.Sandbox.AspNetCore.Client.Models;
namespace OpenIddict.Sandbox.AspNetCore.Client;
public class Worker : IHostedService
{
private readonly IServiceProvider _serviceProvider;
public Worker(IServiceProvider serviceProvider)
=> _serviceProvider = serviceProvider;
public async Task StartAsync(CancellationToken cancellationToken)
{
await using var scope = _serviceProvider.CreateAsyncScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await context.Database.EnsureCreatedAsync(cancellationToken);
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

4
sandbox/OpenIddict.Sandbox.AspNetCore.Client/appsettings.json

@ -1,4 +1,8 @@
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=openiddict-sandbox-aspnetcore-client;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",

24
sandbox/OpenIddict.Sandbox.AspNetCore.Client/web.config

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- To customize the asp.net core module uncomment and edit the following section.
For more info see https://go.microsoft.com/fwlink/?linkid=838655 -->
<!--
<system.webServer>
<handlers>
<remove name="aspNetCore"/>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
</system.webServer>
-->
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxQueryString="32768"/>
</requestFiltering>
</security>
</system.webServer>
</configuration>

4
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Models/ApplicationDbContext.cs

@ -6,7 +6,9 @@ namespace OpenIddict.Sandbox.AspNetCore.Server.Models;
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions options)
: base(options) { }
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{

1
sandbox/OpenIddict.Sandbox.AspNetCore.Server/OpenIddict.Sandbox.AspNetCore.Server.csproj

@ -13,6 +13,7 @@
<ProjectReference Include="..\..\src\OpenIddict.MongoDb\OpenIddict.MongoDb.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Quartz\OpenIddict.Quartz.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Server.AspNetCore\OpenIddict.Server.AspNetCore.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Server.DataProtection\OpenIddict.Server.DataProtection.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Validation.AspNetCore\OpenIddict.Validation.AspNetCore.csproj" />
<ProjectReference Include="..\..\src\OpenIddict.Validation.ServerIntegration\OpenIddict.Validation.ServerIntegration.csproj" />
</ItemGroup>

2
sandbox/OpenIddict.Sandbox.AspNetCore.Server/appsettings.json

@ -1,6 +1,6 @@
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=openiddict-aspnetcore-sandbox;Trusted_Connection=True;MultipleActiveResultSets=true"
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=openiddict-sandbox-aspnetcore-server;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {

24
sandbox/OpenIddict.Sandbox.AspNetCore.Server/web.config

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- To customize the asp.net core module uncomment and edit the following section.
For more info see https://go.microsoft.com/fwlink/?linkid=838655 -->
<!--
<system.webServer>
<handlers>
<remove name="aspNetCore"/>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
</system.webServer>
-->
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxQueryString="32768"/>
</requestFiltering>
</security>
</system.webServer>
</configuration>

35
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -1220,6 +1220,41 @@ Note: when using a dependency injection container supporting middleware resoluti
<data name="ID0317" xml:space="preserve">
<value>The OpenIddict client services cannot be resolved from the DI container.
To register the server services, use 'services.AddOpenIddict().AddClient()'.</value>
</data>
<data name="ID0318" xml:space="preserve">
<value>The core services must be registered when enabling the OpenIddict client feature.
To register the OpenIddict core services, reference the 'OpenIddict.Core' package and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.
Alternatively, you can disable the token storage feature by calling 'services.AddOpenIddict().AddClient().DisableTokenStorage()'.</value>
</data>
<data name="ID0319" xml:space="preserve">
<value>An error occurred while refreshing tokens.
Error: {0}
Error description: {1}
Error URI: {2}</value>
</data>
<data name="ID0320" xml:space="preserve">
<value>An error occurred while preparing the token request.
Error: {0}
Error description: {1}
Error URI: {2}</value>
</data>
<data name="ID0321" xml:space="preserve">
<value>An error occurred while sending the token request.
Error: {0}
Error description: {1}
Error URI: {2}</value>
</data>
<data name="ID0322" xml:space="preserve">
<value>An error occurred while extracting the token response.
Error: {0}
Error description: {1}
Error URI: {2}</value>
</data>
<data name="ID0323" xml:space="preserve">
<value>An error occurred while handling the token response.
Error: {0}
Error description: {1}
Error URI: {2}</value>
</data>
<data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value>

1
src/OpenIddict.AspNetCore/OpenIddict.AspNetCore.csproj

@ -14,6 +14,7 @@
<ItemGroup>
<ProjectReference Include="..\OpenIddict\OpenIddict.csproj" />
<ProjectReference Include="..\OpenIddict.Client.AspNetCore\OpenIddict.Client.AspNetCore.csproj" />
<ProjectReference Include="..\OpenIddict.Client.DataProtection\OpenIddict.Client.DataProtection.csproj" />
<ProjectReference Include="..\OpenIddict.Server.AspNetCore\OpenIddict.Server.AspNetCore.csproj" />
<ProjectReference Include="..\OpenIddict.Server.DataProtection\OpenIddict.Server.DataProtection.csproj" />
<ProjectReference Include="..\OpenIddict.Validation.AspNetCore\OpenIddict.Validation.AspNetCore.csproj" />

15
src/OpenIddict.Client.DataProtection/IOpenIddictClientDataProtectionFormatter.cs

@ -0,0 +1,15 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Security.Claims;
namespace OpenIddict.Client.DataProtection;
public interface IOpenIddictClientDataProtectionFormatter
{
ClaimsPrincipal ReadToken(BinaryReader reader);
void WriteToken(BinaryWriter writer, ClaimsPrincipal principal);
}

38
src/OpenIddict.Client.DataProtection/OpenIddict.Client.DataProtection.csproj

@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp3.1;net5.0;net6.0;netstandard2.0;netstandard2.1</TargetFrameworks>
</PropertyGroup>
<PropertyGroup>
<Description>ASP.NET Core Data Protection integration package for the OpenIddict client services.</Description>
<PackageTags>$(PackageTags);client;dataprotection</PackageTags>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\OpenIddict.Client\OpenIddict.Client.csproj" />
</ItemGroup>
<ItemGroup
Condition=" '$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '3.0')) ">
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionLessThan($(TargetFrameworkVersion), '3.0'))) Or
('$(TargetFrameworkIdentifier)' == '.NETFramework') Or
('$(TargetFrameworkIdentifier)' == '.NETStandard') ">
<PackageReference Include="Microsoft.AspNetCore.DataProtection" />
</ItemGroup>
<ItemGroup>
<Using Include="OpenIddict.Abstractions" />
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" />
<Using Include="OpenIddict.Abstractions.OpenIddictResources" Alias="SR" />
<Using Include="OpenIddict.Client.OpenIddictClientEvents" Static="true" />
<Using Include="OpenIddict.Client.OpenIddictClientHandlers" Static="true" />
<Using Include="OpenIddict.Client.OpenIddictClientHandlerFilters" Static="true" />
<Using Include="OpenIddict.Client.DataProtection.OpenIddictClientDataProtectionHandlers" Static="true" />
</ItemGroup>
</Project>

99
src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionBuilder.cs

@ -0,0 +1,99 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.ComponentModel;
using Microsoft.AspNetCore.DataProtection;
using OpenIddict.Client.DataProtection;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Exposes the necessary methods required to configure the
/// OpenIddict ASP.NET Core Data Protection integration.
/// </summary>
public class OpenIddictClientDataProtectionBuilder
{
/// <summary>
/// Initializes a new instance of <see cref="OpenIddictClientDataProtectionBuilder"/>.
/// </summary>
/// <param name="services">The services collection.</param>
public OpenIddictClientDataProtectionBuilder(IServiceCollection services)
=> Services = services ?? throw new ArgumentNullException(nameof(services));
/// <summary>
/// Gets the services collection.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public IServiceCollection Services { get; }
/// <summary>
/// Amends the default OpenIddict client ASP.NET Core Data Protection configuration.
/// </summary>
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref="OpenIddictClientDataProtectionBuilder"/>.</returns>
public OpenIddictClientDataProtectionBuilder Configure(Action<OpenIddictClientDataProtectionOptions> configuration)
{
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
Services.Configure(configuration);
return this;
}
/// <summary>
/// Configures OpenIddict to use a specific data protection provider
/// instead of relying on the default instance provided by the DI container.
/// </summary>
/// <param name="provider">The data protection provider used to create token protectors.</param>
/// <returns>The <see cref="OpenIddictClientDataProtectionBuilder"/>.</returns>
public OpenIddictClientDataProtectionBuilder UseDataProtectionProvider(IDataProtectionProvider provider)
{
if (provider is null)
{
throw new ArgumentNullException(nameof(provider));
}
return Configure(options => options.DataProtectionProvider = provider);
}
/// <summary>
/// Configures OpenIddict to use a specific formatter instead of relying on the default instance.
/// </summary>
/// <param name="formatter">The formatter used to read and write tokens.</param>
/// <returns>The <see cref="OpenIddictClientDataProtectionBuilder"/>.</returns>
public OpenIddictClientDataProtectionBuilder UseFormatter(IOpenIddictClientDataProtectionFormatter formatter)
{
if (formatter is null)
{
throw new ArgumentNullException(nameof(formatter));
}
return Configure(options => options.Formatter = formatter);
}
/// <summary>
/// Configures OpenIddict to use the default token format (JWT) when issuing new state tokens.
/// </summary>
/// <returns>The <see cref="OpenIddictClientDataProtectionBuilder"/>.</returns>
public OpenIddictClientDataProtectionBuilder PreferDefaultStateTokenFormat()
=> Configure(options => options.PreferDefaultStateTokenFormat = true);
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}

53
src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionConfiguration.cs

@ -0,0 +1,53 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Options;
namespace OpenIddict.Client.DataProtection;
/// <summary>
/// Contains the methods required to ensure that the OpenIddict ASP.NET Core Data Protection configuration is valid.
/// </summary>
public class OpenIddictClientDataProtectionConfiguration : IConfigureOptions<OpenIddictClientOptions>,
IPostConfigureOptions<OpenIddictClientDataProtectionOptions>
{
private readonly IDataProtectionProvider _dataProtectionProvider;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictClientDataProtectionConfiguration"/> class.
/// </summary>
/// <param name="dataProtectionProvider">The ASP.NET Core Data Protection provider.</param>
public OpenIddictClientDataProtectionConfiguration(IDataProtectionProvider dataProtectionProvider)
=> _dataProtectionProvider = dataProtectionProvider;
public void Configure(OpenIddictClientOptions options)
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
// Register the built-in event handlers used by the OpenIddict Data Protection server components.
options.Handlers.AddRange(OpenIddictClientDataProtectionHandlers.DefaultHandlers);
}
/// <summary>
/// Populates the default OpenIddict ASP.NET Core Data Protection server options
/// and ensures that the configuration is in a consistent and valid state.
/// </summary>
/// <param name="name">The name of the options instance to configure, if applicable.</param>
/// <param name="options">The options instance to initialize.</param>
public void PostConfigure(string name, OpenIddictClientDataProtectionOptions options)
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
options.DataProtectionProvider ??= _dataProtectionProvider;
}
}

48
src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionConstants.cs

@ -0,0 +1,48 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
namespace OpenIddict.Client.DataProtection;
public static class OpenIddictClientDataProtectionConstants
{
public static class Properties
{
public const string Audiences = ".audiences";
public const string CodeVerifier = ".code_verifier";
public const string Expires = ".expires";
public const string InternalTokenId = ".internal_token_id";
public const string Issued = ".issued";
public const string Nonce = ".nonce";
public const string OriginalRedirectUri = ".original_redirect_uri";
public const string Presenters = ".presenters";
public const string Resources = ".resources";
public const string Scopes = ".scopes";
public const string StateTokenLifetime = ".state_token_lifetime";
}
public static class Purposes
{
public static class Features
{
public const string ReferenceTokens = "UseReferenceTokens";
}
public static class Formats
{
public const string StateToken = "StateTokenFormat";
}
public static class Handlers
{
public const string Client = "OpenIdConnectClientHandler";
}
public static class Schemes
{
public const string Server = "ASOC";
}
}
}

74
src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionExtensions.cs

@ -0,0 +1,74 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using OpenIddict.Client;
using OpenIddict.Client.DataProtection;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Exposes extensions allowing to register the OpenIddict ASP.NET Core Data Protection client services.
/// </summary>
public static class OpenIddictClientDataProtectionExtensions
{
/// <summary>
/// Registers the OpenIddict ASP.NET Core Data Protection client services in the DI container
/// and configures OpenIddict to validate and issue ASP.NET Data Protection-based tokens.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref="OpenIddictClientBuilder"/>.</returns>
public static OpenIddictClientDataProtectionBuilder UseDataProtection(this OpenIddictClientBuilder builder)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Services.AddDataProtection();
// Register the built-in server event handlers used by the OpenIddict Data Protection components.
// Note: the order used here is not important, as the actual order is set in the options.
builder.Services.TryAdd(OpenIddictClientDataProtectionHandlers.DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor));
// Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
builder.Services.TryAddEnumerable(new[]
{
ServiceDescriptor.Singleton<IConfigureOptions<OpenIddictClientOptions>, OpenIddictClientDataProtectionConfiguration>(),
ServiceDescriptor.Singleton<IPostConfigureOptions<OpenIddictClientDataProtectionOptions>, OpenIddictClientDataProtectionConfiguration>()
});
return new OpenIddictClientDataProtectionBuilder(builder.Services);
}
/// <summary>
/// Registers the OpenIddict ASP.NET Core Data Protection client services in the DI container
/// and configures OpenIddict to validate and issue ASP.NET Data Protection-based tokens.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="configuration">The configuration delegate used to configure the client services.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref="OpenIddictClientBuilder"/>.</returns>
public static OpenIddictClientBuilder UseDataProtection(
this OpenIddictClientBuilder builder, Action<OpenIddictClientDataProtectionBuilder> configuration)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
if (configuration is null)
{
throw new ArgumentNullException(nameof(configuration));
}
configuration(builder.UseDataProtection());
return builder;
}
}

383
src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionFormatter.cs

@ -0,0 +1,383 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using Properties = OpenIddict.Client.DataProtection.OpenIddictClientDataProtectionConstants.Properties;
namespace OpenIddict.Client.DataProtection;
public class OpenIddictClientDataProtectionFormatter : IOpenIddictClientDataProtectionFormatter
{
public ClaimsPrincipal ReadToken(BinaryReader reader)
{
if (reader is null)
{
throw new ArgumentNullException(nameof(reader));
}
var (principal, properties) = Read(reader);
// Tokens serialized using the ASP.NET Core Data Protection stack are compound
// of both claims and special authentication properties. To ensure existing tokens
// can be reused, well-known properties are manually mapped to their claims equivalents.
return principal
.SetAudiences(GetArrayProperty(properties, Properties.Audiences))
.SetPresenters(GetArrayProperty(properties, Properties.Presenters))
.SetResources(GetArrayProperty(properties, Properties.Resources))
.SetScopes(GetArrayProperty(properties, Properties.Scopes))
.SetClaim(Claims.Private.CodeVerifier, GetProperty(properties, Properties.CodeVerifier))
.SetClaim(Claims.Private.CreationDate, GetProperty(properties, Properties.Issued))
.SetClaim(Claims.Private.ExpirationDate, GetProperty(properties, Properties.Expires))
.SetClaim(Claims.Private.Nonce, GetProperty(properties, Properties.Nonce))
.SetClaim(Claims.Private.RedirectUri, GetProperty(properties, Properties.OriginalRedirectUri))
.SetClaim(Claims.Private.StateTokenLifetime, GetProperty(properties, Properties.StateTokenLifetime))
.SetClaim(Claims.Private.TokenId, GetProperty(properties, Properties.InternalTokenId));
static (ClaimsPrincipal principal, IReadOnlyDictionary<string, string> properties) Read(BinaryReader reader)
{
// Read the version of the format used to serialize the ticket.
var version = reader.ReadInt32();
if (version != 5)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0287));
}
// Read the authentication scheme associated to the ticket.
_ = reader.ReadString();
// Read the number of identities stored in the serialized payload.
var count = reader.ReadInt32();
var identities = new ClaimsIdentity[count];
for (var index = 0; index != count; ++index)
{
identities[index] = ReadIdentity(reader);
}
var properties = ReadProperties(reader);
return (new ClaimsPrincipal(identities), properties);
}
static ClaimsIdentity ReadIdentity(BinaryReader reader)
{
var identity = new ClaimsIdentity(
authenticationType: reader.ReadString(),
nameType: ReadWithDefault(reader, ClaimsIdentity.DefaultNameClaimType),
roleType: ReadWithDefault(reader, ClaimsIdentity.DefaultRoleClaimType));
// Read the number of claims contained in the serialized identity.
var count = reader.ReadInt32();
for (int index = 0; index != count; ++index)
{
var claim = ReadClaim(reader, identity);
identity.AddClaim(claim);
}
// Determine whether the identity has a bootstrap context attached.
if (reader.ReadBoolean())
{
identity.BootstrapContext = reader.ReadString();
}
// Determine whether the identity has an actor identity attached.
if (reader.ReadBoolean())
{
identity.Actor = ReadIdentity(reader);
}
return identity;
}
static Claim ReadClaim(BinaryReader reader, ClaimsIdentity identity)
{
var type = ReadWithDefault(reader, identity.NameClaimType);
var value = reader.ReadString();
var valueType = ReadWithDefault(reader, ClaimValueTypes.String);
var issuer = ReadWithDefault(reader, ClaimsIdentity.DefaultIssuer);
var originalIssuer = ReadWithDefault(reader, issuer);
var claim = new Claim(type, value, valueType, issuer, originalIssuer, identity);
// Read the number of properties stored in the claim.
var count = reader.ReadInt32();
for (var index = 0; index != count; ++index)
{
var key = reader.ReadString();
var propertyValue = reader.ReadString();
claim.Properties.Add(key, propertyValue);
}
return claim;
}
static IReadOnlyDictionary<string, string> ReadProperties(BinaryReader reader)
{
// Read the version of the format used to serialize the properties.
var version = reader.ReadInt32();
if (version != 1)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0287));
}
var count = reader.ReadInt32();
var properties = new Dictionary<string, string>(count, StringComparer.Ordinal);
for (var index = 0; index != count; ++index)
{
properties.Add(reader.ReadString(), reader.ReadString());
}
return properties;
}
static string ReadWithDefault(BinaryReader reader, string defaultValue)
{
var value = reader.ReadString();
if (string.Equals(value, "\0", StringComparison.Ordinal))
{
return defaultValue;
}
return value;
}
static string? GetProperty(IReadOnlyDictionary<string, string> properties, string name)
=> properties.TryGetValue(name, out var value) ? value : null;
static ImmutableArray<string> GetArrayProperty(IReadOnlyDictionary<string, string> properties, string name)
{
if (properties.TryGetValue(name, out var value))
{
using var document = JsonDocument.Parse(value);
var builder = ImmutableArray.CreateBuilder<string>(document.RootElement.GetArrayLength());
foreach (var element in document.RootElement.EnumerateArray())
{
var item = element.GetString();
if (string.IsNullOrEmpty(item))
{
continue;
}
builder.Add(item);
}
return builder.ToImmutable();
}
return ImmutableArray.Create<string>();
}
}
public void WriteToken(BinaryWriter writer, ClaimsPrincipal principal)
{
if (writer is null)
{
throw new ArgumentNullException(nameof(writer));
}
if (principal is null)
{
throw new ArgumentNullException(nameof(principal));
}
var properties = new Dictionary<string, string>();
// Unlike ASP.NET Core Data Protection-based tokens, tokens serialized using the new format
// can't include authentication properties. To ensure tokens can be used with previous versions
// of OpenIddict (1.x/2.x), well-known claims are manually mapped to their properties equivalents.
SetProperty(properties, Properties.Issued, principal.GetClaim(Claims.Private.CreationDate));
SetProperty(properties, Properties.Expires, principal.GetClaim(Claims.Private.ExpirationDate));
SetProperty(properties, Properties.StateTokenLifetime, principal.GetClaim(Claims.Private.StateTokenLifetime));
SetProperty(properties, Properties.InternalTokenId, principal.GetTokenId());
SetProperty(properties, Properties.CodeVerifier, principal.GetClaim(Claims.Private.CodeVerifier));
SetProperty(properties, Properties.Nonce, principal.GetClaim(Claims.Private.Nonce));
SetProperty(properties, Properties.OriginalRedirectUri, principal.GetClaim(Claims.Private.RedirectUri));
SetArrayProperty(properties, Properties.Audiences, principal.GetAudiences());
SetArrayProperty(properties, Properties.Presenters, principal.GetPresenters());
SetArrayProperty(properties, Properties.Resources, principal.GetResources());
SetArrayProperty(properties, Properties.Scopes, principal.GetScopes());
// Copy the principal and exclude the claim that were mapped to authentication properties.
principal = principal.Clone(claim => claim.Type is not (
Claims.Private.Audience or
Claims.Private.CodeVerifier or
Claims.Private.CreationDate or
Claims.Private.ExpirationDate or
Claims.Private.Nonce or
Claims.Private.Presenter or
Claims.Private.RedirectUri or
Claims.Private.Resource or
Claims.Private.Scope or
Claims.Private.StateTokenLifetime or
Claims.Private.TokenId));
Write(writer, principal.Identity?.AuthenticationType, principal, properties);
writer.Flush();
// Note: the following local methods closely matches the logic used by ASP.NET Core's
// authentication stack and MUST NOT be modified to ensure tokens encrypted using
// the OpenID Connect server middleware can be read by OpenIddict (and vice-versa).
static void Write(BinaryWriter writer, string? scheme, ClaimsPrincipal principal, IReadOnlyDictionary<string, string> properties)
{
// Write the version of the format used to serialize the ticket.
writer.Write(/* version: */ 5);
writer.Write(scheme ?? string.Empty);
// Write the number of identities contained in the principal.
writer.Write(principal.Identities.Count());
foreach (var identity in principal.Identities)
{
WriteIdentity(writer, identity);
}
WriteProperties(writer, properties);
}
static void WriteIdentity(BinaryWriter writer, ClaimsIdentity identity)
{
writer.Write(identity.AuthenticationType ?? string.Empty);
WriteWithDefault(writer, identity.NameClaimType, ClaimsIdentity.DefaultNameClaimType);
WriteWithDefault(writer, identity.RoleClaimType, ClaimsIdentity.DefaultRoleClaimType);
// Write the number of claims contained in the identity.
writer.Write(identity.Claims.Count());
foreach (var claim in identity.Claims)
{
WriteClaim(writer, claim);
}
var bootstrap = identity.BootstrapContext as string;
if (!string.IsNullOrEmpty(bootstrap))
{
writer.Write(true);
writer.Write(bootstrap);
}
else
{
writer.Write(false);
}
if (identity.Actor is not null)
{
writer.Write(true);
WriteIdentity(writer, identity.Actor);
}
else
{
writer.Write(false);
}
}
static void WriteClaim(BinaryWriter writer, Claim claim)
{
if (writer is null)
{
throw new ArgumentNullException(nameof(writer));
}
if (claim is null)
{
throw new ArgumentNullException(nameof(claim));
}
WriteWithDefault(writer, claim.Type, claim.Subject?.NameClaimType ?? ClaimsIdentity.DefaultNameClaimType);
writer.Write(claim.Value);
WriteWithDefault(writer, claim.ValueType, ClaimValueTypes.String);
WriteWithDefault(writer, claim.Issuer, ClaimsIdentity.DefaultIssuer);
WriteWithDefault(writer, claim.OriginalIssuer, claim.Issuer);
// Write the number of properties contained in the claim.
writer.Write(claim.Properties.Count);
foreach (var property in claim.Properties)
{
writer.Write(property.Key ?? string.Empty);
writer.Write(property.Value ?? string.Empty);
}
}
static void WriteProperties(BinaryWriter writer, IReadOnlyDictionary<string, string> properties)
{
// Write the version of the format used to serialize the properties.
writer.Write(/* version: */ 1);
writer.Write(properties.Count);
foreach (var property in properties)
{
writer.Write(property.Key ?? string.Empty);
writer.Write(property.Value ?? string.Empty);
}
}
static void WriteWithDefault(BinaryWriter writer, string value, string defaultValue)
=> writer.Write(string.Equals(value, defaultValue, StringComparison.Ordinal) ? "\0" : value);
static void SetProperty(IDictionary<string, string> properties, string name, string? value)
{
if (string.IsNullOrEmpty(value))
{
properties.Remove(name);
}
else
{
properties[name] = value;
}
}
static void SetArrayProperty(IDictionary<string, string> properties, string name, ImmutableArray<string> values)
{
if (values.IsDefaultOrEmpty)
{
properties.Remove(name);
}
else
{
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false
});
writer.WriteStartArray();
foreach (var value in values)
{
writer.WriteStringValue(value);
}
writer.WriteEndArray();
writer.Flush();
properties[name] = Encoding.UTF8.GetString(stream.ToArray());
}
}
}
}

201
src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.Protection.cs

@ -0,0 +1,201 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
using System.Security.Claims;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using static OpenIddict.Client.DataProtection.OpenIddictClientDataProtectionConstants.Purposes;
using static OpenIddict.Client.OpenIddictClientHandlers.Protection;
using Schemes = OpenIddict.Client.DataProtection.OpenIddictClientDataProtectionConstants.Purposes.Schemes;
namespace OpenIddict.Client.DataProtection;
public static partial class OpenIddictClientDataProtectionHandlers
{
public static class Protection
{
public static ImmutableArray<OpenIddictClientHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create(
/*
* Token validation:
*/
ValidateDataProtectionToken.Descriptor,
/*
* Token generation:
*/
GenerateDataProtectionToken.Descriptor);
/// <summary>
/// Contains the logic responsible for validating tokens generated using Data Protection.
/// </summary>
public class ValidateDataProtectionToken : IOpenIddictClientHandler<ValidateTokenContext>
{
private readonly IOptionsMonitor<OpenIddictClientDataProtectionOptions> _options;
public ValidateDataProtectionToken(IOptionsMonitor<OpenIddictClientDataProtectionOptions> options)
=> _options = options ?? throw new ArgumentNullException(nameof(options));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.UseSingletonHandler<ValidateDataProtectionToken>()
.SetOrder(ValidateIdentityModelToken.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateTokenContext context)
{
// If a principal was already attached, don't overwrite it.
if (context.Principal is not null)
{
return default;
}
// Note: ASP.NET Core Data Protection tokens always start with "CfDJ8", that corresponds
// to the base64 representation of the magic "09 F0 C9 F0" header identifying DP payloads.
if (!context.Token.StartsWith("CfDJ8", StringComparison.Ordinal))
{
return default;
}
// Note: unlike the equivalent handler in the server stack, the logic used here
// is simpler as only state tokens are currently supported by the client stack.
var principal = context.ValidTokenTypes.Count switch
{
// If no valid token type was set, all supported token types are allowed.
0 => ValidateToken(TokenTypeHints.StateToken),
_ when context.ValidTokenTypes.Contains(TokenTypeHints.StateToken)
=> ValidateToken(TokenTypeHints.StateToken),
_ => null // The token type is not supported by the Data Protection integration (e.g identity tokens).
};
if (principal is null)
{
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2004),
uri: SR.FormatID8000(SR.ID2004));
return default;
}
context.Principal = principal;
context.Logger.LogTrace(SR.GetResourceString(SR.ID6152), context.Token, context.Principal.Claims);
return default;
ClaimsPrincipal? ValidateToken(string type)
{
// Create a Data Protection protector using the provider registered in the options.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(type switch
{
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
TokenTypeHints.StateToken when !string.IsNullOrEmpty(context.TokenId)
=> new[] { Handlers.Client, Formats.StateToken, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.StateToken => new[] { Handlers.Client, Formats.StateToken, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
});
try
{
using var buffer = new MemoryStream(protector.Unprotect(Base64UrlEncoder.DecodeBytes(context.Token)));
using var reader = new BinaryReader(buffer);
// Note: since the data format relies on a data protector using different "purposes" strings
// per token type, the token processed at this stage is guaranteed to be of the expected type.
return _options.CurrentValue.Formatter.ReadToken(reader)?.SetTokenType(type);
}
catch (Exception exception)
{
context.Logger.LogTrace(exception, SR.GetResourceString(SR.ID6153), context.Token);
return null;
}
}
}
}
/// <summary>
/// Contains the logic responsible for generating a token using Data Protection.
/// </summary>
public class GenerateDataProtectionToken : IOpenIddictClientHandler<GenerateTokenContext>
{
private readonly IOptionsMonitor<OpenIddictClientDataProtectionOptions> _options;
public GenerateDataProtectionToken(IOptionsMonitor<OpenIddictClientDataProtectionOptions> options)
=> _options = options ?? throw new ArgumentNullException(nameof(options));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<GenerateTokenContext>()
.UseSingletonHandler<GenerateDataProtectionToken>()
.SetOrder(GenerateIdentityModelToken.Descriptor.Order - 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(GenerateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If an access token was already attached by another handler, don't overwrite it.
if (!string.IsNullOrEmpty(context.Token))
{
return default;
}
if (context.TokenType switch
{
TokenTypeHints.StateToken => _options.CurrentValue.PreferDefaultStateTokenFormat,
_ => true // The token type is not supported by the Data Protection integration (e.g identity tokens).
})
{
return default;
}
// Create a Data Protection protector using the provider registered in the options.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(context.TokenType switch
{
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
TokenTypeHints.StateToken when !context.Options.DisableTokenStorage
=> new[] { Handlers.Client, Formats.StateToken, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.StateToken => new[] { Handlers.Client, Formats.StateToken, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
});
using var buffer = new MemoryStream();
using var writer = new BinaryWriter(buffer);
_options.CurrentValue.Formatter.WriteToken(writer, context.Principal);
context.Token = Base64UrlEncoder.Encode(protector.Protect(buffer.ToArray()));
context.Logger.LogTrace(SR.GetResourceString(SR.ID6013), context.TokenType,
context.Token, context.Principal.Claims);
return default;
}
}
}
}

17
src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.cs

@ -0,0 +1,17 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
using System.ComponentModel;
namespace OpenIddict.Client.DataProtection;
[EditorBrowsable(EditorBrowsableState.Never)]
public static partial class OpenIddictClientDataProtectionHandlers
{
public static ImmutableArray<OpenIddictClientHandlerDescriptor> DefaultHandlers { get; }
= ImmutableArray.CreateRange(Protection.DefaultHandlers);
}

36
src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionOptions.cs

@ -0,0 +1,36 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using Microsoft.AspNetCore.DataProtection;
namespace OpenIddict.Client.DataProtection;
/// <summary>
/// Provides various settings needed to configure the OpenIddict
/// ASP.NET Core Data Protection server integration.
/// </summary>
public class OpenIddictClientDataProtectionOptions
{
/// <summary>
/// Gets or sets the data protection provider used to create the default
/// data protectors used by the OpenIddict Data Protection client services.
/// When this property is set to <see langword="null"/>, the data protection provider
/// is directly retrieved from the dependency injection container.
/// </summary>
public IDataProtectionProvider DataProtectionProvider { get; set; } = default!;
/// <summary>
/// Gets or sets the formatter used to read and write Data Protection tokens.
/// </summary>
public IOpenIddictClientDataProtectionFormatter Formatter { get; set; }
= new OpenIddictClientDataProtectionFormatter();
/// <summary>
/// Gets or sets a boolean indicating whether the default state token format should be
/// used when issuing new state tokens. This property is set to <see langword="false"/> by default.
/// </summary>
public bool PreferDefaultStateTokenFormat { get; set; }
}

10
src/OpenIddict.Client/OpenIddictClientBuilder.cs

@ -960,6 +960,16 @@ public class OpenIddictClientBuilder
return Configure(options => options.Registrations.Add(registration));
}
/// <summary>
/// Disables token storage, so that no database entry is created
/// for the tokens and codes returned by the OpenIddict client.
/// Using this option is generally NOT recommended as it prevents
/// the tokens from being revoked (if needed).
/// </summary>
/// <returns>The <see cref="OpenIddictClientBuilder"/>.</returns>
public OpenIddictClientBuilder DisableTokenStorage()
=> Configure(options => options.DisableTokenStorage = true);
/// <summary>
/// Sets the relative or absolute URLs associated to the redirection endpoint.
/// If an empty array is specified, the endpoint will be considered disabled.

12
src/OpenIddict.Client/OpenIddictClientEvents.Protection.cs

@ -34,6 +34,18 @@ public static partial class OpenIddictClientEvents
set => Transaction.Request = value;
}
/// <summary>
/// Gets or sets a boolean indicating whether a token entry
/// should be created to persist token metadata in a database.
/// </summary>
public bool CreateTokenEntry { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the token payload
/// should be persisted alongside the token metadata in the database.
/// </summary>
public bool PersistTokenPayload { get; set; }
/// <summary>
/// Gets or sets the security principal used to create the token.
/// </summary>

3
src/OpenIddict.Client/OpenIddictClientExtensions.cs

@ -49,8 +49,11 @@ public static class OpenIddictClientExtensions
builder.Services.TryAddSingleton<RequireStateTokenGenerated>();
builder.Services.TryAddSingleton<RequireStateTokenPrincipal>();
builder.Services.TryAddSingleton<RequireStateTokenValidated>();
builder.Services.TryAddSingleton<RequireTokenEntryCreated>();
builder.Services.TryAddSingleton<RequireTokenPayloadPersisted>();
builder.Services.TryAddSingleton<RequireTokenRequest>();
builder.Services.TryAddSingleton<RequireTokenResponse>();
builder.Services.TryAddSingleton<RequireTokenStorageEnabled>();
builder.Services.TryAddSingleton<RequireUserinfoRequest>();
builder.Services.TryAddSingleton<RequireUserinfoResponse>();
builder.Services.TryAddSingleton<RequireUserinfoTokenExtracted>();

48
src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs

@ -220,6 +220,38 @@ public static class OpenIddictClientHandlerFilters
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no token entry is created in the database.
/// </summary>
public class RequireTokenEntryCreated : IOpenIddictClientHandlerFilter<GenerateTokenContext>
{
public ValueTask<bool> IsActiveAsync(GenerateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(context.CreateTokenEntry);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if the token payload is not persisted in the database.
/// </summary>
public class RequireTokenPayloadPersisted : IOpenIddictClientHandlerFilter<GenerateTokenContext>
{
public ValueTask<bool> IsActiveAsync(GenerateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(context.PersistTokenPayload);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no token request is expected to be sent.
/// </summary>
@ -252,6 +284,22 @@ public static class OpenIddictClientHandlerFilters
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if token storage was not enabled.
/// </summary>
public class RequireTokenStorageEnabled : IOpenIddictClientHandlerFilter<BaseContext>
{
public ValueTask<bool> IsActiveAsync(BaseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(!context.Options.DisableTokenStorage);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no userinfo request is expected to be sent.
/// </summary>

323
src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs

@ -8,6 +8,7 @@ using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Security.Claims;
using System.Security.Cryptography;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
@ -23,16 +24,21 @@ public static partial class OpenIddictClientHandlers
* Token validation:
*/
ResolveTokenValidationParameters.Descriptor,
ValidateReferenceTokenIdentifier.Descriptor,
ValidateIdentityModelToken.Descriptor,
MapInternalClaims.Descriptor,
RestoreReferenceTokenProperties.Descriptor,
ValidatePrincipal.Descriptor,
ValidateExpirationDate.Descriptor,
ValidateTokenEntry.Descriptor,
/*
* Token generation:
*/
* Token generation:
*/
AttachSecurityCredentials.Descriptor,
GenerateIdentityModelToken.Descriptor);
CreateTokenEntry.Descriptor,
GenerateIdentityModelToken.Descriptor,
ConvertReferenceToken.Descriptor);
/// <summary>
/// Contains the logic responsible for resolving the validation parameters used to validate tokens.
@ -146,6 +152,74 @@ public static partial class OpenIddictClientHandlers
}
}
/// <summary>
/// Contains the logic responsible for validating reference token identifiers.
/// Note: this handler is not used when token storage is disabled.
/// </summary>
public class ValidateReferenceTokenIdentifier : IOpenIddictClientHandler<ValidateTokenContext>
{
private readonly IOpenIddictTokenManager _tokenManager;
public ValidateReferenceTokenIdentifier() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0318));
public ValidateReferenceTokenIdentifier(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireTokenStorageEnabled>()
.UseScopedHandler<ValidateReferenceTokenIdentifier>()
.SetOrder(ResolveTokenValidationParameters.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
public async ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If the reference token cannot be found, don't return an error to allow another handler to validate it.
var token = await _tokenManager.FindByReferenceIdAsync(context.Token);
if (token is null)
{
return;
}
// If the type associated with the token entry doesn't match one of the expected types, return an error.
if (!(context.ValidTokenTypes.Count switch
{
0 => true, // If no specific token type is expected, accept all token types at this stage.
1 => await _tokenManager.HasTypeAsync(token, context.ValidTokenTypes.ElementAt(0)),
_ => await _tokenManager.HasTypeAsync(token, context.ValidTokenTypes.ToImmutableArray())
}))
{
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2004),
uri: SR.FormatID8000(SR.ID2004));
return;
}
var payload = await _tokenManager.GetPayloadAsync(token);
if (string.IsNullOrEmpty(payload))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0026));
}
// Replace the token parameter by the payload resolved from the token entry
// and store the identifier of the reference token so it can be later
// used to restore the properties associated with the token.
context.Token = payload;
context.TokenId = await _tokenManager.GetIdAsync(token);
}
}
/// <summary>
/// Contains the logic responsible for validating tokens generated using IdentityModel.
/// </summary>
@ -157,7 +231,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.UseSingletonHandler<ValidateIdentityModelToken>()
.SetOrder(ResolveTokenValidationParameters.Descriptor.Order + 1_000)
.SetOrder(ValidateReferenceTokenIdentifier.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@ -319,6 +393,54 @@ public static partial class OpenIddictClientHandlers
}
}
/// <summary>
/// Contains the logic responsible for restoring the properties associated with a reference token entry.
/// Note: this handler is not used when token storage is disabled.
/// </summary>
public class RestoreReferenceTokenProperties : IOpenIddictClientHandler<ValidateTokenContext>
{
private readonly IOpenIddictTokenManager _tokenManager;
public RestoreReferenceTokenProperties() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0318));
public RestoreReferenceTokenProperties(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireTokenStorageEnabled>()
.UseScopedHandler<RestoreReferenceTokenProperties>()
.SetOrder(MapInternalClaims.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
public async ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Principal is null || string.IsNullOrEmpty(context.TokenId))
{
return;
}
var token = await _tokenManager.FindByIdAsync(context.TokenId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0021));
// Restore the creation/expiration dates/identifiers from the token entry metadata.
context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
}
}
/// <summary>
/// Contains the logic responsible for rejecting authentication demands for which no valid principal was resolved.
/// </summary>
@ -330,7 +452,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.UseSingletonHandler<ValidatePrincipal>()
.SetOrder(MapInternalClaims.Descriptor.Order + 1_000)
.SetOrder(RestoreReferenceTokenProperties.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@ -411,6 +533,69 @@ public static partial class OpenIddictClientHandlers
}
}
/// <summary>
/// Contains the logic responsible for authentication demands a token whose
/// associated token entry is no longer valid (e.g was revoked).
/// Note: this handler is not used when token storage is disabled.
/// </summary>
public class ValidateTokenEntry : IOpenIddictClientHandler<ValidateTokenContext>
{
private readonly IOpenIddictTokenManager _tokenManager;
public ValidateTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139));
public ValidateTokenEntry(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireTokenStorageEnabled>()
.UseScopedHandler<ValidateTokenEntry>()
.SetOrder(ValidateExpirationDate.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
var identifier = context.Principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
return;
}
var token = await _tokenManager.FindByIdAsync(identifier);
if (token is null || !await _tokenManager.HasStatusAsync(token, Statuses.Valid))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), identifier);
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2019),
uri: SR.FormatID8000(SR.ID2019));
return;
}
// Restore the creation/expiration dates/identifiers from the token entry metadata.
context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
}
}
/// <summary>
/// Contains the logic responsible for resolving the signing and encryption credentials used to protect tokens.
/// </summary>
@ -443,6 +628,64 @@ public static partial class OpenIddictClientHandlers
}
}
/// <summary>
/// Contains the logic responsible for creating a token entry.
/// Note: this handler is not used when token storage is disabled.
/// </summary>
public class CreateTokenEntry : IOpenIddictClientHandler<GenerateTokenContext>
{
private readonly IOpenIddictTokenManager _tokenManager;
public CreateTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0318));
public CreateTokenEntry(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<GenerateTokenContext>()
.AddFilter<RequireTokenStorageEnabled>()
.AddFilter<RequireTokenEntryCreated>()
.UseScopedHandler<CreateTokenEntry>()
.SetOrder(AttachSecurityCredentials.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(GenerateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var descriptor = new OpenIddictTokenDescriptor
{
AuthorizationId = context.Principal.GetAuthorizationId(),
CreationDate = context.Principal.GetCreationDate(),
ExpirationDate = context.Principal.GetExpirationDate(),
Principal = context.Principal,
Status = Statuses.Valid,
Subject = null,
Type = context.TokenType
};
// Tokens produced by the client stack cannot have an application attached.
var token = await _tokenManager.CreateAsync(descriptor) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0019));
var identifier = await _tokenManager.GetIdAsync(token);
// Attach the token identifier to the principal so that it can be stored in the token.
context.Principal.SetTokenId(identifier);
context.Logger.LogTrace(SR.GetResourceString(SR.ID6012), context.TokenType, identifier);
}
}
/// <summary>
/// Contains the logic responsible for generating a token using IdentityModel.
/// </summary>
@ -454,7 +697,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<GenerateTokenContext>()
.UseSingletonHandler<GenerateIdentityModelToken>()
.SetOrder(AttachSecurityCredentials.Descriptor.Order + 1_000)
.SetOrder(CreateTokenEntry.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@ -531,5 +774,73 @@ public static partial class OpenIddictClientHandlers
return default;
}
}
/// <summary>
/// Contains the logic responsible for converting the token to a reference token.
/// Note: this handler is not used when token storage is disabled.
/// </summary>
public class ConvertReferenceToken : IOpenIddictClientHandler<GenerateTokenContext>
{
private readonly IOpenIddictTokenManager _tokenManager;
public ConvertReferenceToken() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0318));
public ConvertReferenceToken(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<GenerateTokenContext>()
.AddFilter<RequireTokenStorageEnabled>()
.AddFilter<RequireTokenPayloadPersisted>()
.UseScopedHandler<ConvertReferenceToken>()
.SetOrder(GenerateIdentityModelToken.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(GenerateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var identifier = context.Principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0009));
}
var token = await _tokenManager.FindByIdAsync(identifier) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0021));
var descriptor = new OpenIddictTokenDescriptor();
await _tokenManager.PopulateAsync(descriptor, token);
// Attach the generated token to the token entry.
descriptor.Payload = context.Token;
descriptor.Principal = context.Principal;
var data = new byte[256 / 8];
#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS
RandomNumberGenerator.Fill(data);
#else
using var generator = RandomNumberGenerator.Create();
generator.GetBytes(data);
#endif
descriptor.ReferenceId = Base64UrlEncoder.Encode(data);
await _tokenManager.UpdateAsync(token, descriptor);
// Replace the returned token by the reference identifier.
context.Token = descriptor.ReferenceId;
context.Logger.LogTrace(SR.GetResourceString(SR.ID6014), context.TokenType, identifier, descriptor.ReferenceId);
}
}
}
}

59
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -80,6 +80,8 @@ public static partial class OpenIddictClientHandlers
ValidateUserinfoTokenWellknownClaims.Descriptor,
ValidateUserinfoTokenSubject.Descriptor,
RedeemStateTokenEntry.Descriptor,
/*
* Challenge processing:
*/
@ -1191,7 +1193,8 @@ public static partial class OpenIddictClientHandlers
// Resolve the hash algorithm corresponding to the signing algorithm. If an
// instance of the BCL hash algorithm cannot be resolved, throw an exception.
var algorithm = GetHashAlgorithm(name) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0293));
using var algorithm = GetHashAlgorithm(name) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0293));
// If a frontchannel access token was returned in the authorization response,
// ensure the at_hash claim matches the hash of the actual access token.
@ -2204,7 +2207,8 @@ public static partial class OpenIddictClientHandlers
// Resolve the hash algorithm corresponding to the signing algorithm. If an
// instance of the BCL hash algorithm cannot be resolved, throw an exception.
var algorithm = GetHashAlgorithm(name) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0295));
using var algorithm = GetHashAlgorithm(name) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0295));
var hash = context.BackchannelIdentityTokenPrincipal.GetClaim(Claims.AccessTokenHash);
if (string.IsNullOrEmpty(hash))
@ -2901,6 +2905,55 @@ public static partial class OpenIddictClientHandlers
}
}
/// <summary>
/// Contains the logic responsible for redeeming the token entry corresponding to the received state token.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class RedeemStateTokenEntry : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly IOpenIddictTokenManager _tokenManager;
public RedeemStateTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0318));
public RedeemStateTokenEntry(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireTokenStorageEnabled>()
.UseScopedHandler<RedeemStateTokenEntry>()
.SetOrder(ValidateUserinfoTokenSubject.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.StateTokenPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
// Extract the token identifier from the state token principal.
// If no token identifier can be found, this indicates that the token has no backing database entry.
var identifier = context.StateTokenPrincipal.GetTokenId();
if (!string.IsNullOrEmpty(identifier))
{
// Mark the token as redeemed to prevent future reuses.
var token = await _tokenManager.FindByIdAsync(identifier);
if (token is not null)
{
await _tokenManager.TryRedeemAsync(token);
}
}
}
}
/// <summary>
/// Contains the logic responsible for rejecting invalid challenge demands.
/// </summary>
@ -3870,6 +3923,8 @@ public static partial class OpenIddictClientHandlers
var notification = new GenerateTokenContext(context.Transaction)
{
CreateTokenEntry = !context.Options.DisableTokenStorage,
PersistTokenPayload = !context.Options.DisableTokenStorage,
Principal = context.StateTokenPrincipal!,
TokenType = TokenTypeHints.StateToken
};

8
src/OpenIddict.Client/OpenIddictClientOptions.cs

@ -98,4 +98,12 @@ public class OpenIddictClientOptions
ValidateAudience = false,
ValidateLifetime = false
};
/// <summary>
/// Gets or sets a boolean indicating whether token storage should be disabled.
/// When disabled, no database entry is created for the tokens created by the
/// OpenIddict client services. Using this option is generally NOT recommended
/// as it prevents the tokens from being revoked (if needed).
/// </summary>
public bool DisableTokenStorage { get; set; }
}

10
src/OpenIddict.Client/OpenIddictClientService.cs

@ -358,7 +358,7 @@ public class OpenIddictClientService
if (context.IsRejected)
{
throw new OpenIddictExceptions.GenericException(
SR.FormatID0163(context.Error, context.ErrorDescription, context.ErrorUri),
SR.FormatID0319(context.Error, context.ErrorDescription, context.ErrorUri),
context.Error, context.ErrorDescription, context.ErrorUri);
}
@ -487,7 +487,7 @@ public class OpenIddictClientService
if (context.IsRejected)
{
throw new OpenIddictExceptions.GenericException(
SR.FormatID0152(context.Error, context.ErrorDescription, context.ErrorUri),
SR.FormatID0320(context.Error, context.ErrorDescription, context.ErrorUri),
context.Error, context.ErrorDescription, context.ErrorUri);
}
@ -509,7 +509,7 @@ public class OpenIddictClientService
if (context.IsRejected)
{
throw new OpenIddictExceptions.GenericException(
SR.FormatID0153(context.Error, context.ErrorDescription, context.ErrorUri),
SR.FormatID0321(context.Error, context.ErrorDescription, context.ErrorUri),
context.Error, context.ErrorDescription, context.ErrorUri);
}
@ -531,7 +531,7 @@ public class OpenIddictClientService
if (context.IsRejected)
{
throw new OpenIddictExceptions.GenericException(
SR.FormatID0154(context.Error, context.ErrorDescription, context.ErrorUri),
SR.FormatID0322(context.Error, context.ErrorDescription, context.ErrorUri),
context.Error, context.ErrorDescription, context.ErrorUri);
}
@ -556,7 +556,7 @@ public class OpenIddictClientService
if (context.IsRejected)
{
throw new OpenIddictExceptions.GenericException(
SR.FormatID0155(context.Error, context.ErrorDescription, context.ErrorUri),
SR.FormatID0323(context.Error, context.ErrorDescription, context.ErrorUri),
context.Error, context.ErrorDescription, context.ErrorUri);
}

1
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs

@ -15,7 +15,6 @@ public static class OpenIddictServerDataProtectionConstants
public const string Audiences = ".audiences";
public const string CodeChallenge = ".code_challenge";
public const string CodeChallengeMethod = ".code_challenge_method";
public const string DataProtector = ".data_protector";
public const string DeviceCodeId = ".device_code_id";
public const string DeviceCodeLifetime = ".device_code_lifetime";
public const string Expires = ".expires";

88
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionFormatter.cs

@ -34,21 +34,21 @@ public class OpenIddictServerDataProtectionFormatter : IOpenIddictServerDataProt
.SetResources(GetArrayProperty(properties, Properties.Resources))
.SetScopes(GetArrayProperty(properties, Properties.Scopes))
.SetClaim(Claims.Private.AccessTokenLifetime, GetProperty(properties, Properties.AccessTokenLifetime))
.SetClaim(Claims.Private.AccessTokenLifetime, GetProperty(properties, Properties.AccessTokenLifetime))
.SetClaim(Claims.Private.AuthorizationCodeLifetime, GetProperty(properties, Properties.AuthorizationCodeLifetime))
.SetClaim(Claims.Private.AuthorizationId, GetProperty(properties, Properties.InternalAuthorizationId))
.SetClaim(Claims.Private.CodeChallenge, GetProperty(properties, Properties.CodeChallenge))
.SetClaim(Claims.Private.CodeChallengeMethod, GetProperty(properties, Properties.CodeChallengeMethod))
.SetClaim(Claims.Private.CreationDate, GetProperty(properties, Properties.Issued))
.SetClaim(Claims.Private.DeviceCodeId, GetProperty(properties, Properties.DeviceCodeId))
.SetClaim(Claims.Private.DeviceCodeLifetime, GetProperty(properties, Properties.DeviceCodeLifetime))
.SetClaim(Claims.Private.IdentityTokenLifetime, GetProperty(properties, Properties.IdentityTokenLifetime))
.SetClaim(Claims.Private.ExpirationDate, GetProperty(properties, Properties.Expires))
.SetClaim(Claims.Private.Nonce, GetProperty(properties, Properties.Nonce))
.SetClaim(Claims.Private.RedirectUri, GetProperty(properties, Properties.OriginalRedirectUri))
.SetClaim(Claims.Private.RefreshTokenLifetime, GetProperty(properties, Properties.RefreshTokenLifetime))
.SetClaim(Claims.Private.TokenId, GetProperty(properties, Properties.InternalTokenId))
.SetClaim(Claims.Private.UserCodeLifetime, GetProperty(properties, Properties.UserCodeLifetime));
.SetClaim(Claims.Private.AuthorizationId, GetProperty(properties, Properties.InternalAuthorizationId))
.SetClaim(Claims.Private.CodeChallenge, GetProperty(properties, Properties.CodeChallenge))
.SetClaim(Claims.Private.CodeChallengeMethod, GetProperty(properties, Properties.CodeChallengeMethod))
.SetClaim(Claims.Private.CreationDate, GetProperty(properties, Properties.Issued))
.SetClaim(Claims.Private.DeviceCodeId, GetProperty(properties, Properties.DeviceCodeId))
.SetClaim(Claims.Private.DeviceCodeLifetime, GetProperty(properties, Properties.DeviceCodeLifetime))
.SetClaim(Claims.Private.IdentityTokenLifetime, GetProperty(properties, Properties.IdentityTokenLifetime))
.SetClaim(Claims.Private.ExpirationDate, GetProperty(properties, Properties.Expires))
.SetClaim(Claims.Private.Nonce, GetProperty(properties, Properties.Nonce))
.SetClaim(Claims.Private.RedirectUri, GetProperty(properties, Properties.OriginalRedirectUri))
.SetClaim(Claims.Private.RefreshTokenLifetime, GetProperty(properties, Properties.RefreshTokenLifetime))
.SetClaim(Claims.Private.TokenId, GetProperty(properties, Properties.InternalTokenId))
.SetClaim(Claims.Private.UserCodeLifetime, GetProperty(properties, Properties.UserCodeLifetime));
static (ClaimsPrincipal principal, IReadOnlyDictionary<string, string> properties) Read(BinaryReader reader)
{
@ -209,51 +209,51 @@ public class OpenIddictServerDataProtectionFormatter : IOpenIddictServerDataProt
// can't include authentication properties. To ensure tokens can be used with previous versions
// of OpenIddict (1.x/2.x), well-known claims are manually mapped to their properties equivalents.
SetProperty(properties, Properties.Issued, principal.GetClaim(Claims.Private.CreationDate));
SetProperty(properties, Properties.Issued, principal.GetClaim(Claims.Private.CreationDate));
SetProperty(properties, Properties.Expires, principal.GetClaim(Claims.Private.ExpirationDate));
SetProperty(properties, Properties.AccessTokenLifetime, principal.GetClaim(Claims.Private.AccessTokenLifetime));
SetProperty(properties, Properties.AccessTokenLifetime, principal.GetClaim(Claims.Private.AccessTokenLifetime));
SetProperty(properties, Properties.AuthorizationCodeLifetime, principal.GetClaim(Claims.Private.AuthorizationCodeLifetime));
SetProperty(properties, Properties.DeviceCodeLifetime, principal.GetClaim(Claims.Private.DeviceCodeLifetime));
SetProperty(properties, Properties.IdentityTokenLifetime, principal.GetClaim(Claims.Private.IdentityTokenLifetime));
SetProperty(properties, Properties.RefreshTokenLifetime, principal.GetClaim(Claims.Private.RefreshTokenLifetime));
SetProperty(properties, Properties.UserCodeLifetime, principal.GetClaim(Claims.Private.UserCodeLifetime));
SetProperty(properties, Properties.DeviceCodeLifetime, principal.GetClaim(Claims.Private.DeviceCodeLifetime));
SetProperty(properties, Properties.IdentityTokenLifetime, principal.GetClaim(Claims.Private.IdentityTokenLifetime));
SetProperty(properties, Properties.RefreshTokenLifetime, principal.GetClaim(Claims.Private.RefreshTokenLifetime));
SetProperty(properties, Properties.UserCodeLifetime, principal.GetClaim(Claims.Private.UserCodeLifetime));
SetProperty(properties, Properties.CodeChallenge, principal.GetClaim(Claims.Private.CodeChallenge));
SetProperty(properties, Properties.CodeChallenge, principal.GetClaim(Claims.Private.CodeChallenge));
SetProperty(properties, Properties.CodeChallengeMethod, principal.GetClaim(Claims.Private.CodeChallengeMethod));
SetProperty(properties, Properties.InternalAuthorizationId, principal.GetAuthorizationId());
SetProperty(properties, Properties.InternalTokenId, principal.GetTokenId());
SetProperty(properties, Properties.InternalTokenId, principal.GetTokenId());
SetProperty(properties, Properties.DeviceCodeId, principal.GetClaim(Claims.Private.DeviceCodeId));
SetProperty(properties, Properties.Nonce, principal.GetClaim(Claims.Private.Nonce));
SetProperty(properties, Properties.DeviceCodeId, principal.GetClaim(Claims.Private.DeviceCodeId));
SetProperty(properties, Properties.Nonce, principal.GetClaim(Claims.Private.Nonce));
SetProperty(properties, Properties.OriginalRedirectUri, principal.GetClaim(Claims.Private.RedirectUri));
SetArrayProperty(properties, Properties.Audiences, principal.GetAudiences());
SetArrayProperty(properties, Properties.Audiences, principal.GetAudiences());
SetArrayProperty(properties, Properties.Presenters, principal.GetPresenters());
SetArrayProperty(properties, Properties.Resources, principal.GetResources());
SetArrayProperty(properties, Properties.Scopes, principal.GetScopes());
SetArrayProperty(properties, Properties.Resources, principal.GetResources());
SetArrayProperty(properties, Properties.Scopes, principal.GetScopes());
// Copy the principal and exclude the claim that were mapped to authentication properties.
principal = principal.Clone(claim => claim.Type is not (
Claims.Private.AccessTokenLifetime or
Claims.Private.Audience or
Claims.Private.AccessTokenLifetime or
Claims.Private.Audience or
Claims.Private.AuthorizationCodeLifetime or
Claims.Private.AuthorizationId or
Claims.Private.CodeChallenge or
Claims.Private.CodeChallengeMethod or
Claims.Private.CreationDate or
Claims.Private.DeviceCodeId or
Claims.Private.DeviceCodeLifetime or
Claims.Private.ExpirationDate or
Claims.Private.IdentityTokenLifetime or
Claims.Private.Nonce or
Claims.Private.Presenter or
Claims.Private.RedirectUri or
Claims.Private.RefreshTokenLifetime or
Claims.Private.Resource or
Claims.Private.Scope or
Claims.Private.TokenId or
Claims.Private.AuthorizationId or
Claims.Private.CodeChallenge or
Claims.Private.CodeChallengeMethod or
Claims.Private.CreationDate or
Claims.Private.DeviceCodeId or
Claims.Private.DeviceCodeLifetime or
Claims.Private.ExpirationDate or
Claims.Private.IdentityTokenLifetime or
Claims.Private.Nonce or
Claims.Private.Presenter or
Claims.Private.RedirectUri or
Claims.Private.RefreshTokenLifetime or
Claims.Private.Resource or
Claims.Private.Scope or
Claims.Private.TokenId or
Claims.Private.UserCodeLifetime));
Write(writer, principal.Identity?.AuthenticationType, principal, properties);

1
src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConstants.cs

@ -15,7 +15,6 @@ public static class OpenIddictValidationDataProtectionConstants
public const string Audiences = ".audiences";
public const string CodeChallenge = ".code_challenge";
public const string CodeChallengeMethod = ".code_challenge_method";
public const string DataProtector = ".data_protector";
public const string DeviceCodeId = ".device_code_id";
public const string DeviceCodeLifetime = ".device_code_lifetime";
public const string Expires = ".expires";

28
src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionFormatter.cs

@ -32,21 +32,21 @@ public class OpenIddictValidationDataProtectionFormatter : IOpenIddictValidation
.SetResources(GetArrayProperty(properties, Properties.Resources))
.SetScopes(GetArrayProperty(properties, Properties.Scopes))
.SetClaim(Claims.Private.AccessTokenLifetime, GetProperty(properties, Properties.AccessTokenLifetime))
.SetClaim(Claims.Private.AccessTokenLifetime, GetProperty(properties, Properties.AccessTokenLifetime))
.SetClaim(Claims.Private.AuthorizationCodeLifetime, GetProperty(properties, Properties.AuthorizationCodeLifetime))
.SetClaim(Claims.Private.AuthorizationId, GetProperty(properties, Properties.InternalAuthorizationId))
.SetClaim(Claims.Private.CodeChallenge, GetProperty(properties, Properties.CodeChallenge))
.SetClaim(Claims.Private.CodeChallengeMethod, GetProperty(properties, Properties.CodeChallengeMethod))
.SetClaim(Claims.Private.CreationDate, GetProperty(properties, Properties.Issued))
.SetClaim(Claims.Private.DeviceCodeId, GetProperty(properties, Properties.DeviceCodeId))
.SetClaim(Claims.Private.DeviceCodeLifetime, GetProperty(properties, Properties.DeviceCodeLifetime))
.SetClaim(Claims.Private.IdentityTokenLifetime, GetProperty(properties, Properties.IdentityTokenLifetime))
.SetClaim(Claims.Private.ExpirationDate, GetProperty(properties, Properties.Expires))
.SetClaim(Claims.Private.Nonce, GetProperty(properties, Properties.Nonce))
.SetClaim(Claims.Private.RedirectUri, GetProperty(properties, Properties.OriginalRedirectUri))
.SetClaim(Claims.Private.RefreshTokenLifetime, GetProperty(properties, Properties.RefreshTokenLifetime))
.SetClaim(Claims.Private.TokenId, GetProperty(properties, Properties.InternalTokenId))
.SetClaim(Claims.Private.UserCodeLifetime, GetProperty(properties, Properties.UserCodeLifetime));
.SetClaim(Claims.Private.AuthorizationId, GetProperty(properties, Properties.InternalAuthorizationId))
.SetClaim(Claims.Private.CodeChallenge, GetProperty(properties, Properties.CodeChallenge))
.SetClaim(Claims.Private.CodeChallengeMethod, GetProperty(properties, Properties.CodeChallengeMethod))
.SetClaim(Claims.Private.CreationDate, GetProperty(properties, Properties.Issued))
.SetClaim(Claims.Private.DeviceCodeId, GetProperty(properties, Properties.DeviceCodeId))
.SetClaim(Claims.Private.DeviceCodeLifetime, GetProperty(properties, Properties.DeviceCodeLifetime))
.SetClaim(Claims.Private.IdentityTokenLifetime, GetProperty(properties, Properties.IdentityTokenLifetime))
.SetClaim(Claims.Private.ExpirationDate, GetProperty(properties, Properties.Expires))
.SetClaim(Claims.Private.Nonce, GetProperty(properties, Properties.Nonce))
.SetClaim(Claims.Private.RedirectUri, GetProperty(properties, Properties.OriginalRedirectUri))
.SetClaim(Claims.Private.RefreshTokenLifetime, GetProperty(properties, Properties.RefreshTokenLifetime))
.SetClaim(Claims.Private.TokenId, GetProperty(properties, Properties.InternalTokenId))
.SetClaim(Claims.Private.UserCodeLifetime, GetProperty(properties, Properties.UserCodeLifetime));
static (ClaimsPrincipal principal, IReadOnlyDictionary<string, string> properties) Read(BinaryReader reader)
{

Loading…
Cancel
Save