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.
319 lines
13 KiB
319 lines
13 KiB
using System.Globalization;
|
|
using System.Security.Cryptography;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using OpenIddict.Abstractions;
|
|
using OpenIddict.Sandbox.AspNetCore.Server.Models;
|
|
using static OpenIddict.Abstractions.OpenIddictConstants;
|
|
|
|
namespace OpenIddict.Sandbox.AspNetCore.Server;
|
|
|
|
public class Worker : IHostedService
|
|
{
|
|
private readonly IServiceProvider _serviceProvider;
|
|
|
|
public Worker(IServiceProvider serviceProvider)
|
|
=> _serviceProvider = serviceProvider;
|
|
|
|
public async Task StartAsync(CancellationToken cancellationToken)
|
|
{
|
|
var scope = _serviceProvider.CreateScope();
|
|
|
|
try
|
|
{
|
|
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
|
await context.Database.EnsureCreatedAsync(cancellationToken);
|
|
|
|
await RegisterApplicationsAsync(scope.ServiceProvider);
|
|
await RegisterScopesAsync(scope.ServiceProvider);
|
|
}
|
|
|
|
finally
|
|
{
|
|
if (scope is IAsyncDisposable disposable)
|
|
{
|
|
await disposable.DisposeAsync();
|
|
}
|
|
|
|
else
|
|
{
|
|
scope.Dispose();
|
|
}
|
|
}
|
|
|
|
static async Task RegisterApplicationsAsync(IServiceProvider provider)
|
|
{
|
|
var manager = provider.GetRequiredService<IOpenIddictApplicationManager>();
|
|
|
|
if (await manager.FindByClientIdAsync("console") is null)
|
|
{
|
|
await manager.CreateAsync(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"
|
|
},
|
|
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.Device,
|
|
Permissions.Endpoints.Introspection,
|
|
Permissions.Endpoints.Token,
|
|
Permissions.GrantTypes.AuthorizationCode,
|
|
Permissions.GrantTypes.DeviceCode,
|
|
Permissions.GrantTypes.RefreshToken,
|
|
Permissions.ResponseTypes.Code,
|
|
Permissions.Scopes.Email,
|
|
Permissions.Scopes.Profile,
|
|
Permissions.Scopes.Roles,
|
|
Permissions.Prefixes.Scope + "demo_api"
|
|
},
|
|
Requirements =
|
|
{
|
|
Requirements.Features.ProofKeyForCodeExchange
|
|
}
|
|
});
|
|
}
|
|
|
|
if (await manager.FindByClientIdAsync("mvc") is null)
|
|
{
|
|
await manager.CreateAsync(new OpenIddictApplicationDescriptor
|
|
{
|
|
ApplicationType = ApplicationTypes.Web,
|
|
ClientId = "mvc",
|
|
ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654",
|
|
ClientType = ClientTypes.Confidential,
|
|
ConsentType = ConsentTypes.Explicit,
|
|
DisplayName = "MVC client application",
|
|
DisplayNames =
|
|
{
|
|
[CultureInfo.GetCultureInfo("fr-FR")] = "Application cliente MVC"
|
|
},
|
|
#if SUPPORTS_PEM_ENCODED_KEY_IMPORT
|
|
JsonWebKeySet = new JsonWebKeySet
|
|
{
|
|
Keys =
|
|
{
|
|
// On supported platforms, this application authenticates by generating JWT client
|
|
// assertions that are signed using a signing key instead of using a client secret.
|
|
//
|
|
// 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-----
|
|
"""))
|
|
}
|
|
},
|
|
#endif
|
|
RedirectUris =
|
|
{
|
|
new Uri("https://localhost:44381/callback/login/local")
|
|
},
|
|
PostLogoutRedirectUris =
|
|
{
|
|
new Uri("https://localhost:44381/callback/logout/local")
|
|
},
|
|
Permissions =
|
|
{
|
|
Permissions.Endpoints.Authorization,
|
|
Permissions.Endpoints.Logout,
|
|
Permissions.Endpoints.Token,
|
|
Permissions.GrantTypes.AuthorizationCode,
|
|
Permissions.GrantTypes.RefreshToken,
|
|
Permissions.ResponseTypes.Code,
|
|
Permissions.Scopes.Email,
|
|
Permissions.Scopes.Profile,
|
|
Permissions.Scopes.Roles,
|
|
Permissions.Prefixes.Scope + "demo_api"
|
|
},
|
|
Requirements =
|
|
{
|
|
Requirements.Features.ProofKeyForCodeExchange
|
|
}
|
|
});
|
|
}
|
|
|
|
if (await manager.FindByClientIdAsync("winforms") is null)
|
|
{
|
|
await manager.CreateAsync(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"
|
|
},
|
|
RedirectUris =
|
|
{
|
|
new Uri("com.openiddict.sandbox.winforms.client:/callback/login/local")
|
|
},
|
|
Permissions =
|
|
{
|
|
Permissions.Endpoints.Authorization,
|
|
Permissions.Endpoints.Token,
|
|
Permissions.GrantTypes.AuthorizationCode,
|
|
Permissions.GrantTypes.RefreshToken,
|
|
Permissions.ResponseTypes.Code,
|
|
Permissions.Scopes.Email,
|
|
Permissions.Scopes.Profile,
|
|
Permissions.Scopes.Roles,
|
|
Permissions.Prefixes.Scope + "demo_api"
|
|
},
|
|
Requirements =
|
|
{
|
|
Requirements.Features.ProofKeyForCodeExchange
|
|
}
|
|
});
|
|
}
|
|
|
|
if (await manager.FindByClientIdAsync("wpf") is null)
|
|
{
|
|
await manager.CreateAsync(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"
|
|
},
|
|
RedirectUris =
|
|
{
|
|
new Uri("com.openiddict.sandbox.wpf.client:/callback/login/local")
|
|
},
|
|
Permissions =
|
|
{
|
|
Permissions.Endpoints.Authorization,
|
|
Permissions.Endpoints.Token,
|
|
Permissions.GrantTypes.AuthorizationCode,
|
|
Permissions.GrantTypes.RefreshToken,
|
|
Permissions.ResponseTypes.Code,
|
|
Permissions.Scopes.Email,
|
|
Permissions.Scopes.Profile,
|
|
Permissions.Scopes.Roles,
|
|
Permissions.Prefixes.Scope + "demo_api"
|
|
},
|
|
Requirements =
|
|
{
|
|
Requirements.Features.ProofKeyForCodeExchange
|
|
}
|
|
});
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
await manager.CreateAsync(new OpenIddictApplicationDescriptor
|
|
{
|
|
ClientId = "resource_server",
|
|
ClientSecret = "80B552BB-4CD8-48DA-946E-0815E0147DD2",
|
|
ClientType = ClientTypes.Confidential,
|
|
Permissions =
|
|
{
|
|
Permissions.Endpoints.Introspection
|
|
}
|
|
});
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
await manager.CreateAsync(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.Device,
|
|
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
|
|
},
|
|
Settings =
|
|
{
|
|
// Use a shorter access token lifetime for tokens issued to the Postman application.
|
|
[Settings.TokenLifetimes.AccessToken] = TimeSpan.FromMinutes(10).ToString("c", CultureInfo.InvariantCulture)
|
|
}
|
|
});
|
|
}
|
|
|
|
#if SUPPORTS_PEM_ENCODED_KEY_IMPORT
|
|
static ECDsaSecurityKey GetECDsaSigningKey(ReadOnlySpan<char> key)
|
|
{
|
|
var algorithm = ECDsa.Create();
|
|
algorithm.ImportFromPem(key);
|
|
|
|
return new ECDsaSecurityKey(algorithm);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static async Task RegisterScopesAsync(IServiceProvider provider)
|
|
{
|
|
var manager = provider.GetRequiredService<IOpenIddictScopeManager>();
|
|
|
|
if (await manager.FindByNameAsync("demo_api") is null)
|
|
{
|
|
await manager.CreateAsync(new OpenIddictScopeDescriptor
|
|
{
|
|
DisplayName = "Demo API access",
|
|
DisplayNames =
|
|
{
|
|
[CultureInfo.GetCultureInfo("fr-FR")] = "Accès à l'API de démo"
|
|
},
|
|
Name = "demo_api",
|
|
Resources =
|
|
{
|
|
"resource_server"
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
|
}
|
|
|