Versatile OpenID Connect stack for ASP.NET Core and Microsoft.Owin (compatible with ASP.NET 4.6.1)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

789 lines
35 KiB

using System.Globalization;
using System.Net.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using OpenIddict.Sandbox.AspNetCore.Server.Models;
using OpenIddict.Sandbox.AspNetCore.Server.Services;
using Quartz;
using static OpenIddict.Abstractions.OpenIddictConstants;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMvc();
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlite($"Filename={Path.Combine(Path.GetTempPath(), "openiddict-sandbox-aspnetcore-server.sqlite3")}");
options.UseOpenIddict();
});
// Register the Identity builder.Services.
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// OpenIddict offers native integration with Quartz.NET to perform scheduled tasks
// (like pruning orphaned authorizations/tokens from the database) at regular intervals.
builder.Services.AddQuartz(options =>
{
options.UseSimpleTypeLoader();
options.UseInMemoryStore();
});
// Register the Quartz.NET service and configure it to block shutdown until jobs are complete.
builder.Services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);
builder.Services.AddOpenIddict()
// Register the OpenIddict core components.
.AddCore(options =>
{
// Configure OpenIddict to use the Entity Framework Core stores and models.
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
// 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 =>
{
// Note: this sample uses the code flow, but you can enable the other flows if necessary.
options.AllowAuthorizationCodeFlow();
// 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 and use the identity of the current
// assembly as a more specific user agent, which can be useful when dealing with
// providers that use the user agent as a way to throttle requests (e.g Reddit).
options.UseSystemNetHttp()
.SetProductInformation(typeof(Program).Assembly);
// Register the Web providers integrations.
//
// Note: to mitigate mix-up attacks, it's recommended to use a unique redirection endpoint
// URI per provider, unless all the registered providers support returning a special "iss"
// parameter containing their URL as part of authorization responses. For more information,
// see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.4.
options.UseWebProviders()
.AddGitHub(options =>
{
options.SetClientId("c4ade52327b01ddacff3")
.SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122")
.SetRedirectUri("callback/login/github");
});
})
// Register the OpenIddict server components.
.AddServer(options =>
{
// Enable the endpoints that will be used by the client applications.
options.SetAuthorizationEndpointUris("connect/authorize")
.SetDeviceAuthorizationEndpointUris("connect/device")
.SetEndSessionEndpointUris("connect/endsession")
.SetEndUserVerificationEndpointUris("connect/verify")
.SetIntrospectionEndpointUris("connect/introspect")
.SetPushedAuthorizationEndpointUris("connect/par")
.SetRevocationEndpointUris("connect/revoke")
.SetTokenEndpointUris("connect/token")
.SetUserInfoEndpointUris("connect/userinfo");
// Enable the flows that will be used by the client applications.
options.AllowAuthorizationCodeFlow()
.AllowDeviceAuthorizationFlow()
.AllowHybridFlow()
.AllowImplicitFlow()
.AllowNoneFlow()
.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.AllowTokenExchangeFlow();
// Register the public scopes that will be exposed by the configuration endpoint.
options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, "demo_api");
// Register the signing and encryption credentials.
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
options.UseAspNetCore()
.EnableStatusCodePagesIntegration()
.EnableAuthorizationEndpointPassthrough()
.EnableEndSessionEndpointPassthrough()
.EnableEndUserVerificationEndpointPassthrough()
.EnableTokenEndpointPassthrough()
.EnableUserInfoEndpointPassthrough();
// Note: if you don't want to specify a client_id when sending
// a token or revocation request, uncomment the following line:
//
// options.AcceptAnonymousClients();
// Note: if you want to process authorization and token requests
// that specify non-registered scopes, uncomment the following line:
//
// options.DisableScopeValidation();
// Note: if you don't want to use permissions, you can disable
// permission enforcement by uncommenting the following lines:
//
// options.IgnoreEndpointPermissions()
// .IgnoreGrantTypePermissions()
// .IgnoreResponseTypePermissions()
// .IgnoreScopePermissions();
// Note: when issuing access tokens used by third-party APIs
// you don't own, you can disable access token encryption:
//
// options.DisableAccessTokenEncryption();
// Enable both tls_client_auth and self_signed_tls_client_auth to allow clients
// to authenticate using either PKI certificates or self-signed certificates.
//
// Note: PKI and self-signed certificate authentication can be enabled independently.
options.EnablePublicKeyInfrastructureTlsClientAuthentication(
[
// Root certificate:
X509Certificate2.CreateFromPem($"""
-----BEGIN CERTIFICATE-----
MIIE7jCCAtagAwIBAgIJAN+SZB+xc7usMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV
BAMTB1Jvb3QgQ0EwIBcNMjYwMjAxMTQxNDQzWhgPMjEyNjAyMDIxNDE0NDNaMBIx
EDAOBgNVBAMTB1Jvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
AQC68DOD6IvsJM0mc7n8bYeNaVe8e0ytJCJozdMPNXAe80vMPP4cVUPFvJ/tbSjX
yhREJ9xz2dYgQAhWCaTnEHY4AaE1Tj2rYqotenDQxs18qqaqoZlcaFfkRUPHRH3Q
iS8D8gbxzlYkjxNsfDJRi0cXFxr4wb4FmSP4ES2DFWWAWbN9wt7Tb2uDiHkjSefZ
Pni5F6fN6nE7wgGMYrdrCiiwJf7jEZiIZ60bsiUnJ5VUX6g4ob469CLocH/q/9Yr
Dad9/+YYp6SuHZilsPmW4X0fziuF/RvtsRLw4bw5jwj69KH3Y0jqUMQoyzz2CIJz
cDMB/MLREgcT9jTVB/M5Pl61DCzR/0d4t6RENpkNqpAIVM0Unp0nDuHPwjoeEZn3
vSvUiGpiYY355GaSl05OE3SOKoRHt4lBXvY43y8fRBMOwlNHYn4eO3ZDuzZYzhfs
68ywK4zUy47Qyn1BgNNqc/KC7kzxeLFxqTg2VJgBeXuJfucwzhOFqkOSfpeIGDK9
8MODFlA3usf5LXxQ7DJhkeBgPkW56BUlYVkenm9ORWe77GnoXL95p2HQUEXATHir
unXFPVcHET6hyegvc9AzSTZFQL4RMO3ZV5ESs+JF/YY4ycBc1+WYy2kAuP5sfsGn
mpeKf2Dv9MGFcDxU7iimeM92n7t5lbCRlX8NUaYeQ8jKgQIDAQABo0UwQzASBgNV
HRMBAf8ECDAGAQH/AgEBMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUyHIqQnw/
x6e92OuJ+y+e0LSKSqQwDQYJKoZIhvcNAQELBQADggIBACCOjX4+RyoZt9tVoKdT
uGFoEJUGBUfcYaMdqiDstuSoNrqZXHnilzj33ZbNqj6X6rS1w5qVnkj7ZY4Wu8MP
Sj85Wp9cq0jMv3NfPZfKmJd2K7favGQKvgSPptSl9VgcIrpRam9BG2db1IP053tf
ydBB3w/yI7MTb4fkPqLWtKcuPPM8t9SsxAlKhEm+gbNEsqDX9ZIfxolHEpL2zLOi
a4v7+SlJdVBfo4mj+iLUeZXFRPglAnPQ3CZngfbPsjEklpOCU1v8TnhHwV8jyCgl
oLAceLjdlXHWVfhKU+N0jdAt8V2NPxq+yJ/gPX+J3YOrYRCHMdQZ/OFEUhmkxHNp
UUPkL1VJ9c8ZW2/gszFPyvsh7GHwl43y7bN8doiQVOSj6jZ7uCkQl1oz731fl97b
FqKVyGGx6UUEi57YS7mWsY02qNvYSObOxhSNusX/Ct06XbXS1Pn+co/3FMGMcEVf
IwzboV61sMqRu4l3YD0Z1AxdhXFERMlHBYyyj8CQYIXtnCUoeT40IIR3aFmEG5u8
/lwehTnV4slDDMJMFSW54aENpT1XP4b8m46kioNhxN+7ukdcWnYoapePuiDRboA3
GwRExwFUDiGO8zpnyvV4JTIGu9MZi51O3RbSlIDDhyzFsdQW3PeztwGjZWZAXD+7
qIoAaBbG/12cvNeZH7L9Mcpo
-----END CERTIFICATE-----
"""),
// Intermediate certificate (optional):
X509Certificate2.CreateFromPem($"""
-----BEGIN CERTIFICATE-----
MIIFQzCCAyugAwIBAgIQb6sP8aiPyzYFX0lET/XJOzANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQDEwdSb290IENBMCAXDTI2MDIwMTE0MTQ0M1oYDzIxMjYwMjAyMTQx
NDQzWjAaMRgwFgYDVQQDEw9JbnRlcm1lZGlhdGUgQ0EwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQCfObzu9v5dfio8kCCKpb0vLXUrilcOM6FGVx50rPtc
MjlHNG4GpghoLXjJxrUIsoeGdsCI6W+K3R+5PRlEsbCT3l/0n2/ixW3rN9rO3FOt
VGOHYrE2wI+i1aWP1/w/0bcCbH1J6PLKPv5syzhWWdkoTy2K72gye5Kx3zXkFQoC
uBFMvj3HBgmTngaDRTT1QGsRSlhuvoEiHHAvgoTfYt7bgbRhM5I5upEbXB0cucj7
Ghzws5R2/4qsr/QorwA8l6aNeb1dm0uB+FlMVlGelYMZ76+SjBs1rOxD0qt82h13
BYPBR4gLvNFafOEskFndeP3OkNaQ6kPm+uClj5OxwONnBcy7neJPqZGMtxpApLK3
reK3IZ/ieg1nY9zZ7OkqIzQDt1CeCBQWU3RkpEtVojkRDLCmg+pKSjHtLxUUGiQ3
UHrXO2Yrej8Qpx4JHdKGUfku25r4SSaj2YF61ZIvsDxlOMROfJUFbpQdyAtQPCCq
zpfkVKaCeCiTlifI6AZODngc9c8U+s7vLxjucz/Q4gNHwcgg/mASbjh8A2hYsbW6
qeg7lE9k5t6Lv820FudjfgFiq7k+zIbNsDNy3Y7CSsgBHQQSyNFngg25PPQOon9c
yd3PiFK36OzktnRkcTs98i3fwO2+3pp6qgOSk1Sdx877egszMjBPFzxrBX9CXIpM
yQIDAQABo4GKMIGHMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEG
MB0GA1UdDgQWBBThFsFtfMN62ev+YiMAAddufdjpRzBCBgNVHSMEOzA5gBTIcipC
fD/Hp73Y64n7L57QtIpKpKEWpBQwEjEQMA4GA1UEAxMHUm9vdCBDQYIJAN+SZB+x
c7usMA0GCSqGSIb3DQEBCwUAA4ICAQA6sBGs28FWhKgh6TxZ6U8Lc+iCdc4c9PeM
L5pQQosHekT0oBJK8WdvyXZS95Fz2ddJaKiiQyUKSP4XHpxE+6tBt8OOV0LJJxnx
yKTBZtcSiOFssu2j6aqx3oMotRZJrhuI/5ChExaPwFT1W7aQDIY6lN2KcQ/xndbX
Nts/2nwCvlplfiOGM7XrRMU8b4X+AVWXSksvLXiByrDh9W6WGDsBHyu+FKQVnwmW
QVnshKpwxIsW25JDOhFE8+VHn6yciUKUTqnCFt5HjZpZh00q8hhmlhrNBEdkxA8N
OF7S1uWWftJywqq23qG6pGIDQ1r1dwNzgaeNhmW6QKm2zBXUmuOW//Xt+1wtHrly
bDjXKKSa/zhR9plYPdvGe9PopXwTw/fQWRYcxML6aH+WbWY9AgCHFgY56YCJYZd9
eUIfrvPVJLn8fqwLmsWQtIY+XkAS/YQ4wTQs0zZS3+bdxeGQ6oHIMgxCDiBK8Qcc
RHf+RvYHiBllOJmaRaJHdsauMk9IlYYYpxPPwuWGti9B5HI4JO6bIqmR5Q8x3L/g
tFGMPzvWDTA2+dQcrh7WKULDH9Ngnnoodc6Hb9Iv1yCGYahcS6ARt9BzRyG1+6d9
bq/zCH8KQCjryiTn3ZEpsln/iXtp5nHiLegUc1OoXldrUKAz9V93l61GHUw1kdhD
V+KJceDj3Q==
-----END CERTIFICATE-----
""")
]);
options.EnableSelfSignedTlsClientAuthentication();
// Note: setting a static issuer is mandatory when using mTLS aliases to ensure it not
// dynamically computed based on the request URI, as this would result in two different
// issuers being used (one pointing to the mTLS domain and one pointing to the regular one).
options.SetIssuer("https://localhost:44395/");
// Configure the mTLS endpoint aliases that will be used by client applications opting
// for TLS-based client authentication to communicate with the authorization server:
// the configured URIs MUST point to a domain for which the HTTPS server is configured
// to require the use of client certificates when receiving TLS handshakes from clients.
//
// Using mTLS endpoint aliases is not mandatory but is strongly recommended to avoid
// severely degrading the experience of users of browser-based clients, as TLS client
// authentication can only be enforced globally and not per-client, which would result
// in certificate selection prompts being systematically displayed by browsers.
options.SetMtlsDeviceAuthorizationEndpointAliasUri("https://mtls.dev.localhost:44395/connect/device")
.SetMtlsIntrospectionEndpointAliasUri("https://mtls.dev.localhost:44395/connect/introspect")
.SetMtlsPushedAuthorizationEndpointAliasUri("https://mtls.dev.localhost:44395/connect/par")
.SetMtlsRevocationEndpointAliasUri("https://mtls.dev.localhost:44395/connect/revoke")
.SetMtlsTokenEndpointAliasUri("https://mtls.dev.localhost:44395/connect/token")
.SetMtlsUserInfoEndpointAliasUri("https://mtls.dev.localhost:44395/connect/userinfo");
// While public client applications cannot use mTLS for client authentication, they can use
// mTLS purely as a token binding mechanism: in this case, the refresh tokens issued to
// public clients sending a client certificate are automatically bound to the certificate,
// which requires sending the same certificate when using them to get new access tokens.
options.UseClientCertificateBoundRefreshTokens();
// Optionally, the server stack can be configured to issue client certificate-bound access tokens.
//
// When doing so, the standard "cnf" claim is automatically added to access tokens to inform
// resource servers that a proof of possession derived from the certificate must be provided.
options.UseClientCertificateBoundAccessTokens();
})
// Register the OpenIddict validation components.
.AddValidation(options =>
{
// Configure the audience accepted by this resource server.
// The value MUST match the audience associated with the
// "demo_api" scope, which is used by ResourceController.
options.AddAudiences("resource_server");
// Import the configuration from the local OpenIddict server instance.
options.UseLocalServer();
// Instead of validating the token locally by reading it directly,
// introspection can be used to ask a remote authorization server
// to validate the token (and its attached database entry).
//
// options.UseIntrospection()
// .SetIssuer("https://localhost:44395/")
// .SetClientId("resource_server")
// .SetClientSecret("vVQ-yjr42sXP5VHj6AswkXuS7MU1i2gFjvJjY0TdGMk");
//
// When introspection is used, the System.Net.Http integration must be enabled.
//
// options.UseSystemNetHttp();
// Register the ASP.NET Core host.
options.UseAspNetCore();
// For applications that need immediate access token or authorization
// revocation, the database entry of the received tokens and their
// associated authorizations can be validated for each API call.
// Enabling these options may have a negative impact on performance.
//
// options.EnableAuthorizationEntryValidation();
// options.EnableTokenEntryValidation();
});
builder.Services.AddTransient<IEmailSender, AuthMessageSender>();
builder.Services.AddTransient<ISmsSender, AuthMessageSender>();
// Configure Kestrel to listen on the 44395 port and configure it to enforce mTLS.
//
// Note: depending on the operating system, the mtls.dev.localhost
// subdomain MAY have to be manually mapped to 127.0.0.1 or ::1.
builder.Services.Configure<KestrelServerOptions>(options => options.ListenAnyIP(44395, options =>
{
options.UseHttps(new TlsHandshakeCallbackOptions
{
OnConnection = GetServerAuthenticationOptionsAsync
});
static ValueTask<SslServerAuthenticationOptions> GetServerAuthenticationOptionsAsync(TlsHandshakeCallbackContext context)
{
using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
return ValueTask.FromResult(new SslServerAuthenticationOptions
{
// Require a client certificate for all the requests pointing to the mTLS subdomain.
ClientCertificateRequired = string.Equals(context.ClientHelloInfo.ServerName,
"mtls.dev.localhost", StringComparison.OrdinalIgnoreCase),
// Ignore all the client certificate errors for requests pointing to
// the mTLS-specific domain, even if they indicate that the chain is
// invalid: this is necessary to allow OpenIddict to validate the PKI
// and self-signed certificates using its own per-client chain policies.
RemoteCertificateValidationCallback = (sender, certificate, chain, errors) =>
{
if (string.Equals(context.ClientHelloInfo.ServerName,
"mtls.dev.localhost", StringComparison.OrdinalIgnoreCase))
{
return true;
}
return errors is SslPolicyErrors.None or SslPolicyErrors.RemoteCertificateNotAvailable;
},
// Use the development certificate generated and stored by ASP.NET Core in the user store.
ServerCertificate = store.Certificates
.Find(X509FindType.FindByExtension, "1.3.6.1.4.1.311.84.1.1", validOnly: false)
.Cast<X509Certificate2>()
.Where(static certificate => certificate.NotBefore < TimeProvider.System.GetLocalNow())
.Where(static certificate => certificate.NotAfter > TimeProvider.System.GetLocalNow())
.OrderByDescending(static certificate => certificate.NotAfter)
.FirstOrDefault() ??
throw new InvalidOperationException("The ASP.NET Core HTTPS development certificate was not found.")
});
}
}));
var app = builder.Build();
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseStatusCodePagesWithReExecute("/error");
app.UseRouting();
app.UseRequestLocalization(options =>
{
options.AddSupportedCultures("en-US", "fr-FR");
options.AddSupportedUICultures("en-US", "fr-FR");
options.SetDefaultCulture("en-US");
});
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapDefaultControllerRoute();
// Before starting the host, create the database used to store the application data.
//
// Note: in a real world application, this step should be part of a setup script.
await using (var scope = app.Services.CreateAsyncScope())
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await context.Database.EnsureCreatedAsync();
await RegisterApplicationsAsync(scope.ServiceProvider);
await RegisterScopesAsync(scope.ServiceProvider);
}
await app.RunAsync();
static async Task RegisterApplicationsAsync(IServiceProvider provider)
{
var manager = provider.GetRequiredService<IOpenIddictApplicationManager>();
if (await manager.FindByClientIdAsync("console") is null)
{
var descriptor = new OpenIddictApplicationDescriptor
{
// Note: the application must be registered as a native application to force OpenIddict
// to apply a relaxed redirect_uri validation policy that allows specifying a random port.
ApplicationType = ApplicationTypes.Native,
ClientId = "console",
ClientType = ClientTypes.Public,
ConsentType = ConsentTypes.Systematic,
DisplayName = "Console client application",
DisplayNames =
{
[CultureInfo.GetCultureInfo("fr-FR")] = "Application cliente console"
},
PostLogoutRedirectUris =
{
// Note: the port must not be explicitly specified as it is selected
// dynamically at runtime by the OpenIddict client system integration.
new Uri("http://localhost/callback/logout/local")
},
RedirectUris =
{
// Note: the port must not be explicitly specified as it is selected
// dynamically at runtime by the OpenIddict client system integration.
new Uri("http://localhost/callback/login/local")
},
Permissions =
{
Permissions.Endpoints.Authorization,
Permissions.Endpoints.DeviceAuthorization,
Permissions.Endpoints.Introspection,
Permissions.Endpoints.EndSession,
Permissions.Endpoints.PushedAuthorization,
Permissions.Endpoints.Revocation,
Permissions.Endpoints.Token,
Permissions.GrantTypes.AuthorizationCode,
Permissions.GrantTypes.DeviceCode,
Permissions.GrantTypes.Implicit,
Permissions.GrantTypes.Password,
Permissions.GrantTypes.RefreshToken,
Permissions.GrantTypes.TokenExchange,
Permissions.ResponseTypes.Code,
Permissions.ResponseTypes.CodeIdToken,
Permissions.ResponseTypes.CodeIdTokenToken,
Permissions.ResponseTypes.CodeToken,
Permissions.ResponseTypes.IdToken,
Permissions.ResponseTypes.IdTokenToken,
Permissions.ResponseTypes.None,
Permissions.Scopes.Email,
Permissions.Scopes.Profile,
Permissions.Scopes.Roles
},
Requirements =
{
Requirements.Features.ProofKeyForCodeExchange,
Requirements.Features.PushedAuthorizationRequests
}
};
descriptor.AddScopePermissions("demo_api");
await manager.CreateAsync(descriptor);
}
if (await manager.FindByClientIdAsync("maui") is null)
{
var descriptor = new OpenIddictApplicationDescriptor
{
ApplicationType = ApplicationTypes.Native,
ClientId = "maui",
ClientType = ClientTypes.Public,
ConsentType = ConsentTypes.Systematic,
DisplayName = "MAUI client application",
DisplayNames =
{
[CultureInfo.GetCultureInfo("fr-FR")] = "Application cliente MAUI"
},
PostLogoutRedirectUris =
{
new Uri("com.openiddict.sandbox.maui.client:/callback/logout/local")
},
RedirectUris =
{
new Uri("com.openiddict.sandbox.maui.client:/callback/login/local")
},
Permissions =
{
Permissions.Endpoints.Authorization,
Permissions.Endpoints.EndSession,
Permissions.Endpoints.PushedAuthorization,
Permissions.Endpoints.Token,
Permissions.GrantTypes.AuthorizationCode,
Permissions.GrantTypes.RefreshToken,
Permissions.ResponseTypes.Code,
Permissions.Scopes.Email,
Permissions.Scopes.Profile,
Permissions.Scopes.Roles
},
Requirements =
{
Requirements.Features.ProofKeyForCodeExchange,
Requirements.Features.PushedAuthorizationRequests
}
};
descriptor.AddScopePermissions("demo_api");
await manager.CreateAsync(descriptor);
}
if (await manager.FindByClientIdAsync("mvc") is null)
{
var descriptor = new OpenIddictApplicationDescriptor
{
ApplicationType = ApplicationTypes.Web,
ClientId = "mvc",
ClientSecret = "emCimpdc9SeOaZzN5jzm4_eek-STF6VenfVlKO1_qt0",
ClientType = ClientTypes.Confidential,
ConsentType = ConsentTypes.Systematic,
DisplayName = "MVC client application",
DisplayNames =
{
[CultureInfo.GetCultureInfo("fr-FR")] = "Application cliente MVC"
},
JsonWebKeySet = new JsonWebKeySet
{
Keys =
{
// On supported platforms, this application can authenticate by using a
// self-signed client authentication certificate during the TLS handshake
// (a method known as "mutual TLS" or mTLS).
//
// Note: while the client needs access to the private key, the server only needs
// to know the public part to be able to validate the certificates it receives.
JsonWebKeyConverter.ConvertFromX509SecurityKey(new X509SecurityKey(
X509Certificate2.CreateFromPem($"""
-----BEGIN CERTIFICATE-----
MIIC8zCCAdugAwIBAgIJAIZ9BN3TUnZQMA0GCSqGSIb3DQEBCwUAMCIxIDAeBgNV
BAMTF1NlbGYtc2lnbmVkIGNlcnRpZmljYXRlMCAXDTI2MDIwMjE0MzM0OVoYDzIx
MjYwMjAyMTQzMzQ5WjAiMSAwHgYDVQQDExdTZWxmLXNpZ25lZCBjZXJ0aWZpY2F0
ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOtfKVPM7ghVFh4U/sz4
sTrpaNJGQ2NORqawYxAHwluhr101yIOW7rWvFlFncA64Lkq9SAbFFCVSAbo28c6B
2Mi41jyC4LHQU11jhv08K/3FUuckCuzEpzTnXUhxJHWxrRDVEuvKINGPs1VgVtTT
ra8rjP8s1YRAzCYnByxSx+8GXNGHprylLh0agpWKb2+2FYwDqY5ME2g3xTL9FTUu
FYWTcyspsvN0U1Eo1vlCeOxSYGPRct0MK0AS6eXEGBv+3kCYI7a5+UhQok0WvErF
pjIVo7USISDgKhW9GhTsWN+WywwdG4Kx4V6SB8ZLAHFSBSR3gjWS3TGOyqAWoBXc
znkCAwEAAaMqMCgwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUF
BwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBf5i/S7shmNalVxMuP8/Mk8cOhRRZjnAXd
zz3eOuXu0CH8iY/DwCgss04O2NTxuz87rKiuNKOrtY0oN/G4aFjWPvbgoQ+N1XP1
zvbhqbyo3fQr07FyjWkrIUoHYFQ3JRfL+GPGjWizJsgdpdCRJSK6G9VX8eU3Akjv
YhMRLmbkrH5etOURqFtLpZlxNmLzCpqWIvzRiYyyj74iOipA2I0acgcvkakWn6rE
Wio7luBAZ3dXlukEfHTOg+ft4k0nOlRXPTtASOmyFQBOs6iYJeztHDz6MQnknAPe
+W53US8kLWktspcOQmxhVVH1g1/T4ynl9iX7tzqvUbdYwZNi92+x
-----END CERTIFICATE-----
"""))),
// On supported platforms, this application can also authenticate by
// generating JWT client assertions that are signed using a signing key.
//
// Note: while the client needs access to the private key, the server only needs
// to know the public key to be able to validate the client assertions it receives.
JsonWebKeyConverter.ConvertFromECDsaSecurityKey(GetECDsaSigningKey($"""
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEI23kaVsRRAWIez/pqEZOByJFmlXd
a6iSQ4QqcH23Ir8aYPPX5lsVnBsExNsl7SOYOiIhgTaX6+PTS7yxTnmvSw==
-----END PUBLIC KEY-----
"""))
}
},
RedirectUris =
{
new Uri("https://localhost:44381/callback/login/local")
},
PostLogoutRedirectUris =
{
new Uri("https://localhost:44381/callback/logout/local")
},
Permissions =
{
Permissions.Endpoints.Authorization,
Permissions.Endpoints.EndSession,
Permissions.Endpoints.PushedAuthorization,
Permissions.Endpoints.Token,
Permissions.GrantTypes.AuthorizationCode,
Permissions.GrantTypes.RefreshToken,
Permissions.ResponseTypes.Code,
Permissions.Scopes.Email,
Permissions.Scopes.Profile,
Permissions.Scopes.Roles
},
Requirements =
{
Requirements.Features.ProofKeyForCodeExchange,
Requirements.Features.PushedAuthorizationRequests
}
};
descriptor.AddScopePermissions("demo_api");
await manager.CreateAsync(descriptor);
}
if (await manager.FindByClientIdAsync("winforms") is null)
{
var descriptor = new OpenIddictApplicationDescriptor
{
ApplicationType = ApplicationTypes.Native,
ClientId = "winforms",
ClientType = ClientTypes.Public,
ConsentType = ConsentTypes.Systematic,
DisplayName = "WinForms client application",
DisplayNames =
{
[CultureInfo.GetCultureInfo("fr-FR")] = "Application cliente WinForms"
},
PostLogoutRedirectUris =
{
new Uri("com.openiddict.sandbox.winforms.client:/callback/logout/local")
},
RedirectUris =
{
new Uri("com.openiddict.sandbox.winforms.client:/callback/login/local")
},
Permissions =
{
Permissions.Endpoints.Authorization,
Permissions.Endpoints.EndSession,
Permissions.Endpoints.PushedAuthorization,
Permissions.Endpoints.Token,
Permissions.GrantTypes.AuthorizationCode,
Permissions.GrantTypes.RefreshToken,
Permissions.ResponseTypes.Code,
Permissions.Scopes.Email,
Permissions.Scopes.Profile,
Permissions.Scopes.Roles
},
Requirements =
{
Requirements.Features.ProofKeyForCodeExchange,
Requirements.Features.PushedAuthorizationRequests
}
};
descriptor.AddScopePermissions("demo_api");
await manager.CreateAsync(descriptor);
}
if (await manager.FindByClientIdAsync("wpf") is null)
{
var descriptor = new OpenIddictApplicationDescriptor
{
ApplicationType = ApplicationTypes.Native,
ClientId = "wpf",
ClientType = ClientTypes.Public,
ConsentType = ConsentTypes.Systematic,
DisplayName = "WPF client application",
DisplayNames =
{
[CultureInfo.GetCultureInfo("fr-FR")] = "Application cliente WPF"
},
PostLogoutRedirectUris =
{
new Uri("com.openiddict.sandbox.wpf.client:/callback/logout/local")
},
RedirectUris =
{
new Uri("com.openiddict.sandbox.wpf.client:/callback/login/local")
},
Permissions =
{
Permissions.Endpoints.Authorization,
Permissions.Endpoints.EndSession,
Permissions.Endpoints.PushedAuthorization,
Permissions.Endpoints.Token,
Permissions.GrantTypes.AuthorizationCode,
Permissions.GrantTypes.RefreshToken,
Permissions.ResponseTypes.Code,
Permissions.Scopes.Email,
Permissions.Scopes.Profile,
Permissions.Scopes.Roles
},
Requirements =
{
Requirements.Features.ProofKeyForCodeExchange,
Requirements.Features.PushedAuthorizationRequests
}
};
descriptor.AddScopePermissions("demo_api");
await manager.CreateAsync(descriptor);
}
// Note: when using introspection instead of local token validation,
// an application entry MUST be created to allow the resource server
// to communicate with OpenIddict's introspection endpoint.
if (await manager.FindByClientIdAsync("resource_server") is null)
{
var descriptor = new OpenIddictApplicationDescriptor
{
ClientId = "resource_server",
ClientSecret = "vVQ-yjr42sXP5VHj6AswkXuS7MU1i2gFjvJjY0TdGMk",
ClientType = ClientTypes.Confidential,
Permissions =
{
Permissions.Endpoints.Introspection
}
};
await manager.CreateAsync(descriptor);
}
// To test this sample with Postman, use the following settings:
//
// * Authorization URL: https://localhost:44395/connect/authorize
// * Access token URL: https://localhost:44395/connect/token
// * Client ID: postman
// * Client secret: [blank] (not used with public clients)
// * Scope: openid email profile roles
// * Grant type: authorization code
// * Request access token locally: yes
if (await manager.FindByClientIdAsync("postman") is null)
{
var descriptor = new OpenIddictApplicationDescriptor
{
ApplicationType = ApplicationTypes.Native,
ClientId = "postman",
ClientType = ClientTypes.Public,
ConsentType = ConsentTypes.Systematic,
DisplayName = "Postman",
RedirectUris =
{
new Uri("https://oauth.pstmn.io/v1/callback")
},
Permissions =
{
Permissions.Endpoints.Authorization,
Permissions.Endpoints.DeviceAuthorization,
Permissions.Endpoints.Token,
Permissions.GrantTypes.AuthorizationCode,
Permissions.GrantTypes.DeviceCode,
Permissions.GrantTypes.Password,
Permissions.GrantTypes.RefreshToken,
Permissions.ResponseTypes.Code,
Permissions.Scopes.Email,
Permissions.Scopes.Profile,
Permissions.Scopes.Roles
}
};
// Use a shorter access token lifetime for tokens issued to the Postman application.
descriptor.SetAccessTokenLifetime(TimeSpan.FromMinutes(10));
await manager.CreateAsync(descriptor);
}
static ECDsaSecurityKey GetECDsaSigningKey(ReadOnlySpan<char> key)
{
var algorithm = ECDsa.Create();
algorithm.ImportFromPem(key);
return new ECDsaSecurityKey(algorithm);
}
}
static async Task RegisterScopesAsync(IServiceProvider provider)
{
var manager = provider.GetRequiredService<IOpenIddictScopeManager>();
if (await manager.FindByNameAsync("demo_api") is null)
{
var descriptor = new OpenIddictScopeDescriptor
{
DisplayName = "Demo API access",
DisplayNames =
{
[CultureInfo.GetCultureInfo("fr-FR")] = "Accès à l'API de démo"
},
Name = "demo_api",
Resources =
{
"resource_server"
}
};
await manager.CreateAsync(descriptor);
}
}