+ Visit the github site for doc, source code and issue tracking.
+
+
+ If you have trouble with login, disable Chromium cookies-without-same-site-must-be-secure flag.
+
+ chrome://flags/#cookies-without-same-site-must-be-secure
+
+ This site is running under a free heroku dyno without end-to-end https.
+
+
+ You can sign-in with alice to have reader/writer access, or bob for a read-only access.
+ The password is Pass123$.
+
+```
+
+## UI Options
+### Hide settings menu
+
+To hide the settings menu, unset **menuOptions:showSettings**.
+
+### Hide CIBA grant type
+
+If CIBA is not enabled you can hide the CIBA grant type by unsetting cibaEnabled options.
+
+### Hide coordinate lifetime with user session checkbox
+
+If server side sessions are not enable you can hide the coordinate lifetime with user session checkbox in client tokens section by unsetting serverSideSessionEnabled options.
+
+## Additional resources
+
+* [ASP.NET Core Blazor hosting model configuration](https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-model-configuration?view=aspnetcore-3.1#configuration)
+* [ASP.NET Core Blazor WebAssembly additional security scenarios](https://docs.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/additional-scenarios?view=aspnetcore-3.1#customize-app-routes)
+* [Secure an ASP.NET Core Blazor WebAssembly standalone app with the Authentication library](https://docs.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/standalone-with-authentication-library?view=aspnetcore-3.1#authentication-service-support)
+* [LogLevel Enum](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-3.1)
diff --git a/doc/INTRODUCTION.md b/doc/INTRODUCTION.md
new file mode 100644
index 000000000..ef659b01c
--- /dev/null
+++ b/doc/INTRODUCTION.md
@@ -0,0 +1,11 @@
+# Introduction
+
+Read [TheIdServer](../README.md).
+
+## How to contribute
+
+Read [How to contribute](../CONTRIBUTING.md) and [Contributor Covenant Code of Conduct](../CODE_OF_CONDUCT.md).
+
+## Security Policy
+
+Read [Security Policy](../SECURITY.md).
diff --git a/doc/SERVER.md b/doc/SERVER.md
new file mode 100644
index 000000000..cb3e50d02
--- /dev/null
+++ b/doc/SERVER.md
@@ -0,0 +1,1093 @@
+# TheIdServer Duende Web Server
+
+> TheIdServer use [Duende IdentityServer](https://duendesoftware.com/products/identityserver), for a commercial use you need to [acquire a license](https://duendesoftware.com/products/identityserver#pricing).
+
+The server obtains configuration from *appsettings.json*, *appsettings.{Environment}.json*, command-line arguments, or environment variables.
+
+Read [Configuration in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) for more information.
+
+## Installation
+
+### From Terraform
+
+The [Terraform](https://terraform.io) [Helm](https://helm.sh) module [theidserver](https://registry.terraform.io/modules/Aguafrommars/theidserver/helm/latest) make the deployement of TheIdServer easy.
+To deploy the Duende version choose the [aguacongas/theidserver.duende image](https://hub.docker.com/r/aguacongas/theidserver.duende).
+
+``` hcl
+provider "helm" {
+ kubernetes {
+ config_path = var.kubeconfig_path
+ }
+}
+
+module "theidserver" {
+ source = "Aguafrommars/theidserver/helm"
+
+ host = "theidserver.com"
+ tls_issuer_name = "letsencrypt"
+ tls_issuer_kind = "ClusterIssuer"
+
+ image = {
+ repository = "aguacongas/theidserver.duende"
+ pullPolicy = "Always"
+ tag = "next"
+ }
+}
+```
+
+### From Helm
+
+The [theidserver](https://hub.helm.sh/packages/helm/aguafrommars/theidserver) [Helm](https://helm.sh) chart is available in [hub.helm.sh](https://hub.helm.sh).
+
+#### Install
+
+``` bash
+helm repo add aguafrommars https://aguafrommars.github.io/helm
+helm install aguafrommars theidserver --set theidserver.mysql.db.password=my-P@ssword --set image.repository=aguacongas/theidserver.duende
+```
+
+> By default the helm char install the IS4 version, to install the Duende version your need to set `image.repository=aguacongas/theidserver.duende`.
+
+#### Upgrade
+
+Follow upgrades intstructions in the [chart readme](https://github.com/Aguafrommars/helm/blob/main/charts/theidserver/README.md#upgrade).
+
+### From Docker
+
+A [server's Linux image](https://hub.docker.com/r/aguacongas/theidserver.duende) is available on Docker Hub.
+
+[*sample/MultiTiers/Aguacongas.TheIdServer.Private/Dockerfile.Duende-private*](../../sample/MultiTiers/Aguacongas.TheIdServer.Private/Dockerfile.Duende-private) demonstrates how to create an image from the [server image](https://hub.docker.com/r/aguacongas/theidserver) to run a private Linux server container.
+
+[*sample/MultiTiers/Aguacongas.TheIdServer.Public/Dockerfile.Duende-public*](../../sample/MultiTiers/Aguacongas.TheIdServer.Public/Dockerfile.Duende-public) illustrates how to create an image from the [server image](https://hub.docker.com/r/aguacongas/theidserver) to run a public Linux server container.
+
+Read [Hosting ASP.NET Core images with Docker over HTTPS](https://docs.microsoft.com/en-us/aspnet/core/security/docker-https) to set up the HTTPS certificate.
+
+#### Kubernetes sample
+
+[/sample/Kubernetes/README.md](/sample/Kubernetes/README.md) contains a sample to set up a solution with Kubernetes.
+
+> The sample use the IS4 version but you just need to use `aguacongas/theidserver.duende` as docker image in the deployement file.
+
+### From dotnet new template
+
+The template [TheIdServer.Duende.Template](https://github.com/Aguafrommars/Templates) can be use to setup a TheIdServer solution.
+
+#### Install
+
+```bash
+dotnet new -i TheIdServer.Duende.Template
+```
+
+#### Use
+
+```bash
+> dotnet new tisduende -o TheIdServer
+The template "TheIdServer.Duende" was created successfully.
+
+Processing post-creation actions...
+Running 'dotnet restore' on TheIdServer\TheIdServer.sln...
+ Determining projects to restore...
+ Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\WebAssembly.Net.Http\WebAssembly.Net.Http.csproj (in 114 ms).
+ Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\src\TheIdServer.BlazorApp\TheIdServer.BlazorApp.csproj (in 916 ms).
+ Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\Microsoft.AspNetCore.Components.Testing\Microsoft.AspNetCore.Components.Testing.csproj (in 1.08 sec).
+ Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\src\TheIdServer\TheIdServer.csproj (in 2.03 sec).
+ Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\TheIdServer.Test\TheIdServer.Test.csproj (in 2.04 sec).
+ Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\TheIdServer.IntegrationTest\TheIdServer.IntegrationTest.csproj (in 2.04 sec).
+Restore succeeded.
+```
+
+### From NuGet Packages
+
+If you need more customization, you can use published NuGet packages.
+[sample/MultiTiers](sample/MultiTiers) contains a sample to build server and API from NuGet packages.
+
+> The sample use IS4 version but you just need to remplace IS4 by Duende in package reference to use the Duende version.
+
+### From Github Release
+
+Choose your release in the [list of releases](https://github.com/Aguafrommars/TheIdServer/releases) and download the server zip.
+Unzip in the destination of your choice. Unzip in the destination of your choice. As with any ASP.NET Core web site, it can run in IIS or as a stand-alone server using your chosen platform.
+
+Read [Host and deploy ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/) for more information.
+
+## Configure data protection
+
+[Data protection](DATA_PROTECTION.md) provides details on data protection configuration.
+
+## Configure site
+
+The site name is defined by *SiteOptions:TheIdServer*.
+
+```json
+"SiteOptions": {
+ "Name": "TheIdServer"
+}
+```
+
+The site stylecheets are *wwwroot/lib/bootstrap/css/bootstrap.css* and *wwwroot/css/site.min.css*.
+The site logo is *wwwroot/logo.png*.
+And the favicon is *wwwroot/favicon.ico*.
+
+By replacing those files you can redefined the site style by yours.
+
+### Configure account options
+
+The section *AccountOptions* is bound to [`AccountOptions`](../src/Aguacongas.TheIdServer.Duende/Quickstart/Account/AccountOptions.cs).
+
+```json
+"AccountOptions": {
+ "AllowLocalLogin": true,
+ "AllowRememberLogin": true,
+ "RememberMeLoginDuration": "30.00:00:00",
+ "ShowLogoutPrompt": true,
+ "AutomaticRedirectAfterSignOut": false,
+ "InvalidCredentialsErrorMessage": "Invalid username or password",
+ "ShowForgotPassworLink": true,
+ "ShowRegisterLink": true,
+ "ShowResendEmailConfirmationLink": true
+}
+```
+
+## Configure ASP.Net Core Identity options
+
+The section **IdentityOptions** is bound to the class [`Microsoft.AspNetCore.Identity.IdentityOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.identityoptions).
+So you can set any ASP.Net Core Identity options you want from configuration
+
+```json
+"IdentityOptions": {
+ "User": {
+ "AllowedUserNameCharacters": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+ "
+ },
+ "SignIn": {
+ "RequireConfirmedAccount": true
+ }
+}
+```
+
+## Configure password hashers options
+
+Read [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) to choose and configure your password hasher.
+
+### PBKDF2 Password hasher
+
+`Microsoft.AspNetCore.Identity.PasswordHasher` is the default hasher used by ASP.Net Core Identity.
+You can hash password using PBKDF2 if the [upgrade password hasher](#upgrade-password-hasher) is configured to use `Microsoft.AspNetCore.Identity.PasswordHasher`.
+
+The section **PasswordHasherOptions** is bound to the class [`Microsoft.AspNetCore.Identity.PasswordHasherOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.passwordhasheroptions).
+So you can set any [`Microsoft.AspNetCore.Identity.PasswordHasherOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.passwordhasheroptions) properties you want from configuration.
+
+```json
+"PasswordHasherOptions": {
+ "IterationCount": 600000
+}
+```
+
+### Argon2id password hasher
+
+You can hash password using Argon2id if the [upgrade password hasher](#upgrade-password-hasher) is configured to use `Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasher`.
+
+The section **Argon2PasswordHasherOptions** is bound to the class [`Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasherOptions.cs).
+So you can set any [`Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasherOptions.cs) properties you want from configuration.
+
+```json
+"Argon2PasswordHasherOptions": {
+ "Interations": 2,
+ "Memory": 67108864
+}
+```
+
+### scrypt password hasher
+
+You can hash password using scrypt if the [upgrade password hasher](#upgrade-password-hasher) is configured to use `Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasher`.
+
+The section **ScryptPasswordHasherOptions** is bound to the class [`Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasherOptions.cs).
+So you can set any [`Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.Argon2PasswordHasher/Argon2PasswordHasherOptions.cs) properties you want from configuration.
+
+```json
+"ScryptPasswordHasherOptions": {
+ "IterationCount": 131072,
+ "BlockSize": 8,
+ "ThreadCount": 1
+}
+```
+
+### bcrypt password hasher
+
+You can hash password using bcrypt if the [upgrade password hasher](#upgrade-password-hasher) is configured to use `Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.BcryptPasswordHasher`.
+
+The section **BcryptPasswordHasherOptions** is bound to the class [`Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.BcryptPasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/BcryptPasswordHasherOptions.cs).
+So you can set any [`Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.BcryptPasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.BcryptPasswordHasher/BcryptPasswordHasherOptions.cs) properties you want from configuration.
+
+```json
+"BcryptPasswordHasherOptions": {
+ "WorkFactor": 11
+}
+```
+
+### Upgrade password hasher
+
+Upgrade password hasher is used to manage hash migration between old password hashing algorithm to the new one to use.
+In previous version of TheIdServer password was hashed with PBKDF2 by default ASP.Net Core Identity password hasher with its default configuration.
+Now you can choose between Argon2id, scrypt, bcrypt and PBKDF2 by settings the hasher to use.
+
+Read [Password Hasher to rehash password to a new algorithm for ASP.NET Core Identity.](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/README.md#password-hasher-to-rehash-password-to-a-new-algorithm-for-aspnet-core-identity) for more information.
+
+The section **UpgradePasswordHasherOptions** is bound to the class [`Aguacongas.TheIdServer.Identity.UpgradePasswordHasher.UpgradePasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/UpgradePasswordHasherOptions.cs).
+So you can set any [`Aguacongas.TheIdServer.Identity.UpgradePasswordHasher.UpgradePasswordHasherOptions`](https://github.com/Aguafrommars/TheIdServer/blob/master/src/Identity/Aguacongas.TheIdServer.Identity.UpgradePasswordHasher/UpgradePasswordHasherOptions.cs) properties you want from configuration.
+
+```json
+"UpgradePasswordHasherOptions": {
+ "HashPrefixMaps": {
+ "0": "Microsoft.AspNetCore.Identity.PasswordHasher",
+ "1": "Microsoft.AspNetCore.Identity.PasswordHasher",
+ "162": "Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasher",
+ "12": "Aguacongas.TheIdServer.Identity.ScryptPasswordHasher.ScryptPasswordHasher",
+ "188": "Aguacongas.TheIdServer.Identity.BcryptPasswordHasher.BcryptPasswordHasher"
+ },
+ "UsePasswordHasherTypeName": "Aguacongas.TheIdServer.Identity.Argon2PasswordHasher.Argon2PasswordHasher"
+}
+```
+
+## Configure Duende IdentityServer
+
+The section **IdentityServerOptions** is bound to the class [`Duende.IdentityServer.Configuration.IdentityServerOptions`](https://docs.duendesoftware.com/identityserver/v5/reference/options/).
+So you can set any Duende IdentityServer options you want from configuration (but key management options).
+
+```json
+"IdentityServerOptions": {
+ "Events": {
+ "RaiseErrorEvents": true,
+ "RaiseInformationEvents": true,
+ "RaiseFailureEvents": true,
+ "RaiseSuccessEvents": true
+ },
+ "Endpoints": {
+ "EnableJwtRequestUri": true
+ }
+}
+```
+
+### Discovery document customs entries
+
+You can add customs entries to the genererated discovery document with *IdentityServerOptions* sub section *CustomEntriesOfStringArray*, *CustomEntriesOfString* and *CustomEntriesOfBool*
+
+```json
+"IdentityServerOptions": {
+ "CustomEntriesOfStringArray": {
+ "token_endpoint_auth_signing_alg_values_supported": [
+ "RS256",
+ "ES256",
+ "ES384",
+ "ES512",
+ "PS256",
+ "PS384",
+ "PS512",
+ "RS384",
+ "RS512"
+ ]
+ }
+}
+```
+
+The sample above will add `"token_endpoint_auth_signing_alg_values_supported"` node to the generated document.
+
+### Mutual TLS client certificate options
+
+When Muutal TLS is enabled, you can configure the client certificate authentication options with `CertificateAuthenticationOptions` section.
+
+```json
+"IdentityServerOptions": {
+ "MutualTls": {
+ "Enabled": true
+ }
+},
+"CertificateAuthenticationOptions": {
+ "AllowedCertificateTypes": "All",
+ "ValidateCertificateUse": false,
+ "ValidateValidityPeriod": false,
+ "RevocationMode": "NoCheck"
+}
+```
+
+### Retrieves client certificates fron HTTP request header
+
+When Mutual TLS is enabled the client certificate can be read in PEM format from request header. For exemple if you use a kubernetes NGINX ingress you can configure it to send the client certificate to the backend in the *ssl-client-cert* header.
+See [Client Certificate Authentication](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#client-certificate-authentication).
+
+To retrieve the client certificate from the request header confiure the `MutualTls` sub section like :
+
+```json
+"IdentityServerOptions": {
+ "MutualTls": {
+ "Enabled": true,
+ "PEMHeader": "ssl-client-cert"
+ }
+}
+```
+
+### Configure Server-side sessions
+
+Read [Server-side sessions](SERVER_SIDE_SESSIONS.md)
+
+## Configure stores
+
+### Using Entity Framework Core
+
+The server supports *SqlServer*, *Sqlite*, *MySql*, *PostgreSQL*, *Oracle*, and *InMemory* databases.
+Use **DbType** to the define the database engine.
+
+```json
+"DbType": "SqlServer"
+```
+
+And **ConnectionStrings:DefaultConnection** to define the connection string.
+
+```json
+"ConnectionStrings": {
+ "DefaultConnection": "Data Source=(LocalDb)\\MSSQLLocalDB;database=TheIdServer;trusted_connection=yes;"
+}
+```
+
+> A [devart dotConnect for Oracle](https://www.devart.com/dotconnect/oracle/) license is a requirement for Oracle.
+
+### Using RavenDb
+
+Use **DbType** to the define the RavenDb database engine.
+
+```json
+"DbType": "RavenDb"
+```
+
+And **RavenDbOptions** to define the RavenDb options.
+
+```json
+"RavenDbOptions": {
+ "Urls": [
+ "https://a.ravendb.local",
+ "https://b.ravendb.local",
+ "https://c.ravendb.local"
+ ],
+ "Database": "TheIdServer",
+ "CertificatePath": "cluster.admin.client.certificate.pfx",
+ "CertificatePassword": "p@$$w0rd"
+}
+```
+
+> As no `DbContext` will be registered, you cannot store signing keys in EF but you can choose the `RavenDb` storage kind (see [Configure signin keys](#configure-signing-key)):
+```json
+"IdentityServer": {
+ "Key": {
+ "StorageKind": "RavenDb"
+ }
+},
+"DataProtectionOptions": {
+ "StorageKind": "RavenDb"
+}
+```
+> The server support RavenDb 4.1 and above.
+
+### Using MongoDb
+
+Use **DbType** to the define the RavenDb database engine.
+
+```json
+"DbType": "MongoDb"
+```
+
+And **ConnectionStrings:DefaultConnection** to define the connection string.
+
+```json
+"ConnectionStrings": {
+ "DefaultConnection": "mongodb+srv://theidserver:theidserverpwd@cluster0.fvkfz.mongodb.net/TheIdServer?retryWrites=true&w=majority"
+}
+```
+
+> We cannot used another database than the default database defined in the connection string.
+
+> As no `DbContext` will be registered, you cannot store signing keys in EF but you can choose the `MongoDb` storage kind (see [Configure signin keys](#configure-signing-key)):
+```json
+"IdentityServer": {
+ "Key": {
+ "StorageKind": "MongoDb"
+ }
+},
+"DataProtectionOptions": {
+ "StorageKind": "MongoDb"
+}
+```
+
+### Using the API
+
+![public-private.svg](assets/public-pribate.png)
+
+If you don't want to expose a database with your server, you can set up a second server on a private network accessing the database and use this private server API to access data.
+
+```json
+"Proxy": true,
+"PrivateServerAuthentication": {
+ "Authority": "https://theidserverprivate",
+ "ApiUrl": "https://theidserverprivate/api",
+ "ClientId": "public-server",
+ "ClientSecret": "84137599-13d6-469c-9376-9e372dd2c1bd",
+ "Scope": "theidserveradminapi",
+ "HttpClientName": "is4"
+},
+"SignalR": {
+ "HubUrl": "https://theidserverprivate/providerhub"
+ "HubOptions": {
+ "EnableDetailedErrors": true
+ },
+ "UseMessagePack": true
+}
+```
+
+#### Proxy
+
+Start the server with proxy mode enabled.
+
+#### PrivateServerAuthentication
+
+Defines how to authenticate the public server on private server API.
+
+#### SignalR
+
+Defines the [SignalR client](https://docs.microsoft.com/en-us/aspnet/core/signalr/dotnet-client&tabs=visual-studio) configuration.
+This client is used to update the external provider configuration of a running instance. When an external provider configuration changes, the API sends a SignalR notification to inform other running instances.
+
+For more information, read [Load balancing scenario](https://github.com/Aguafrommars/DymamicAuthProviders/wiki/Load-balancing-scenario).
+
+The SignalR hub accepts requests at */providerhub* and supports the [MessagePack](https://msgpack.org/index.html) protocol.
+
+For more information, read [Use MessagePack Hub Protocol in SignalR for ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/signalr/messagepackhubprotocol).
+
+### Database migration and data seeding
+
+Starting the server with the **/seed** command-line argument creates the database with initial data. Alternatively, configure the server with the following to create a database with initial users, protected resources, identity resources, and clients.
+
+```json
+"Migrate": true,
+"Seed": true
+```
+
+#### Roles
+
+* **Is4-Writer** authorizes users in this role to write data.
+* **Is4-Reader** permits users in this role to read data.
+
+#### Identity resources
+
+* **profile** default profile resource with **role** claim
+* **openid** default OpenID resource
+* **address** default address resource
+* **email** default email resource
+* **phone** default phone resource
+
+#### Users
+
+Users defined in `InitialData:Users` configuration section are loaded and stored to the DB.
+
+Default configuration:
+
+```json
+"InitialData": {
+...
+ "Users": [
+ {
+ "UserName": "alice",
+ "Email": "alice@theidserver.com",
+ "EmailConfirmed": true,
+ "PhoneNumber": "+41766403736",
+ "PhoneNumberConfirmed": true,
+ "Password": "Pass123$",
+ "Roles": [
+ "Is4-Writer",
+ "Is4-Reader"
+ ],
+ "Claims": [
+ {
+ "ClaimType": "name",
+ "ClaimValue": "Alice Smith"
+ },
+ {
+ "ClaimType": "given_name",
+ "ClaimValue": "Alice"
+ },
+ {
+ "ClaimType": "family_name",
+ "ClaimValue": "Smith"
+ },
+ {
+ "ClaimType": "middle_name",
+ "ClaimValue": "Alice Smith"
+ },
+ {
+ "ClaimType": "nickname",
+ "ClaimValue": "alice"
+ },
+ {
+ "ClaimType": "website",
+ "ClaimValue": "http://alice.com"
+ },
+ {
+ "ClaimType": "address",
+ "ClaimValue": "{ \"street_address\": \"One Hacker Way\", \"locality\": \"Heidelberg\", \"postal_code\": \"69118\", \"country\": \"Germany\" }",
+ },
+ {
+ "ClaimType": "birthdate",
+ "ClaimValue": "1970-01-01"
+ },
+ {
+ "ClaimType": "zoneinfo",
+ "ClaimValue": "ch"
+ },
+ {
+ "ClaimType": "gender",
+ "ClaimValue": "female"
+ },
+ {
+ "ClaimType": "profile",
+ "ClaimValue": "http://alice.com/profile"
+ },
+ {
+ "ClaimType": "locale",
+ "ClaimValue": "fr"
+ },
+ {
+ "ClaimType": "picture",
+ "ClaimValue": "http://alice.com/picture"
+ }
+ ]
+ }
+ ]
+}
+```
+
+> A user with *Is4-Writer* and *Is4-Reader* roles is required to use the admin app.
+
+#### Protected resources (API)
+
+Apis defined in `InitialData:Apis` configuration section are loaded and stored to the DB.
+
+Default configuration:
+
+```json
+"InitialData": {
+...
+ "Apis": [
+ {
+ "Name": "theidserveradminapi",
+ "DisplayName": "TheIdServer admin API",
+ "UserClaims": [
+ "name",
+ "role"
+ ],
+ "ApiSecrets": [
+ {
+ "Type": "SharedSecret",
+ "Value": "5b556f7c-b3bc-4b5b-85ab-45eed0cb962d"
+ }
+ ],
+ "Scopes": [
+ "theidserveradminapi"
+ ]
+ }
+ ],
+}
+```
+
+> The api **theidserveradminapi** is required for the admin app.
+
+#### ApiScopes
+
+ApiScopes defined in `InitialData:ApiScopes` configuration section are loaded and stored to the DB.
+
+Default configuration:
+
+```json
+"InitialData": {
+...
+ "ApiScopes": [
+ {
+ "Name": "theidserveradminapi",
+ "DisplayName": "TheIdServer admin API",
+ "UserClaims": [
+ "name",
+ "role"
+ ]
+ }
+ ],
+}
+```
+
+> The scope **theidserveradminapi** is required for the admin app.
+
+#### Clients
+
+Clients defined in `InitialData:Clients` configuration section are loaded and stored to the DB.
+
+Default configuration:
+
+```json
+"InitialData": {
+...
+ "Clients": [
+ {
+ "ClientId": "theidserveradmin",
+ "ClientName": "TheIdServer admin SPA Client",
+ "ClientUri": "https://localhost:5443/",
+ "ClientClaimsPrefix": null,
+ "AllowedGrantTypes": [ "authorization_code" ],
+ "RequirePkce": true,
+ "RequireClientSecret": false,
+ "BackChannelLogoutSessionRequired": false,
+ "FrontChannelLogoutSessionRequired": false,
+ "RedirectUris": [
+ "http://localhost:5001/authentication/login-callback",
+ "https://localhost:5443/authentication/login-callback"
+ ],
+ "PostLogoutRedirectUris": [
+ "http://localhost:5001/authentication/logout-callback",
+ "https://localhost:5443/authentication/logout-callback"
+ ],
+ "AllowedCorsOrigins": [
+ "http://localhost:5001",
+ "https://localhost:5443"
+ ],
+ "AllowedScopes": [
+ "openid",
+ "profile",
+ "theidserveradminapi"
+ ],
+ "AccessTokenType": "Reference"
+ },
+ {
+ "ClientId": "public-server",
+ "ClientName": "Public server Credentials Client",
+ "ClientClaimsPrefix": null,
+ "AllowedGrantTypes": [ "client_credentials" ],
+ "ClientSecrets": [
+ {
+ "Type": "SharedSecret",
+ "Value": "84137599-13d6-469c-9376-9e372dd2c1bd"
+ }
+ ],
+ "Claims": [
+ {
+ "Type": "role",
+ "Value": "Is4-Writer"
+ },
+ {
+ "Type": "role",
+ "Value": "Is4-Reader"
+ }
+ ],
+ "BackChannelLogoutSessionRequired": false,
+ "FrontChannelLogoutSessionRequired": false,
+ "AllowedScopes": [
+ "openid",
+ "profile",
+ "theidserveradminapi"
+ ],
+ "AccessTokenType": "Reference"
+ }
+ ],
+}
+```
+
+> The client **theidserveradmin** is required by the admin app.
+> The client **public-server** is required to call web apis and server side prerendering of the admin app.
+
+## Configure Signing Key
+
+### Keys rotatation (recommanded)
+
+TheIdServer can be configured with a keys rotation mechanism instead of a single key.
+Read [Keys rotation](KEYS_ROTATION.md) to know how to configure it.
+
+```json
+"IdentityServer": {
+ "Key": {
+ "Type": "KeysRotation",
+ "StorageKind": "EntityFramework"
+ }
+}
+```
+
+### From file
+
+```json
+"IdentityServer": {
+ "Key": {
+ "Type": "File",
+ "FilePath": "{path to the .pfx}",
+ "Password": "{.pfx password}"
+ }
+}
+```
+
+### From store
+
+Read [Example: Deploy to Azure Websites](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization#example-deploy-to-azure-websites)
+
+## Configure the Email service
+
+By default, the server uses [SendGrid](https://sendgrid.com/) to send Emails by calling the API at */api/email*
+
+```json
+"SendGridUser": "your user",
+"SendGridKey": "your SendGrid key"
+```
+
+### Use your API
+
+If you prefer to use your Email sender, implement a Web API receiving a POST request with the json:
+
+```json
+{
+ "subject": "Email subject",
+ "message": "Email message",
+ "addresses": [
+ "an-address@aguacongas.con"
+ ]
+}
+```
+
+And update the *EmailApiAuthentication* configuration section:
+
+```json
+"EmailApiAuthentication": {
+ "Authority": "https://localhost:5443",
+ "ApiUrl": "https://localhost:5443/api/email",
+ "ClientId": "public-server",
+ "ClientSecret": "84137599-13d6-469c-9376-9e372dd2c1bd",
+ "Scope": "theidserveradminapi",
+ "HttpClientName": "email"
+}
+```
+
+> If you want to use the same authentication configuration and token for both *EmailApi* and *PrivateServer*, you can simplify it by sharing the same **HttpClientName**.
+
+```json
+"EmailApiAuthentication": {
+ "ApiUrl": "https://localhost:5443/api/email",
+ "HttpClientName": "is4"
+}
+```
+
+## Configure the 2fa authenticator issuer
+
+By default, the issuer for the 2fa authenticator is **Aguacongas.TheIdServer**.
+To update this value, set **AuthenticatorIssuer** with your issuer.
+
+```json
+"AuthenticatorIssuer": "TheIdServer"
+```
+
+## Configure the API
+
+### Authentication
+
+The *ApiAuthentication* section defines the authentication configuration for the API.
+
+```json
+"ApiAuthentication": {
+ "Authority": "https://localhost",
+ "RequireHttpsMetadata": false,
+ "SupportedTokens": "Both",
+ "ApiName": "theidserveradminapi",
+ "ApiSecret": "5b556f7c-b3bc-4b5b-85ab-45eed0cb962d",
+ "EnableCaching": true,
+ "CacheDuration": "0:10:0",
+ "LegacyAudienceValidation": true
+}
+```
+
+### Documentation endpoint
+
+To enable the API documentation, set **EnableOpenApiDoc** to `true`.
+
+```json
+"EnableOpenApiDoc": true
+```
+
+Use the section *SwaggerUiSettings* to configure the swagger client authentication.
+
+```json
+"SwaggerUiSettings": {
+ "Path": "/api/swagger",
+ "OAuth2Client": {
+ "ClientId": "theidserver-swagger",
+ "AppName": "TheIdServer Swagger UI",
+ "UsePkceWithAuthorizationCodeGrant": true
+ },
+ "WithCredentials": true
+}
+```
+
+### CORS
+
+The section *CorsAllowedOrigin* defines allowed CORS origins.
+
+```json
+"CorsAllowedOrigin": [
+ "http://localhost:5001"
+]
+```
+
+## Configure HTTPS
+
+To disable HTTPS, set **DisableHttps** to `false`.
+
+```json
+"DisableHttps": true
+```
+
+If you use a self-signed certificate, you can disable strict-SSL by settings **DisableStrictSsl** to `true`.
+
+```json
+"DisableStrictSsl": true
+```
+
+### Configure Forwarded Headers
+
+The section **ForwardedHeadersOptions** is bound to the class [`Microsoft.AspNetCore.Builder.ForwardedHeadersOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.forwardedheadersoptions).
+
+```json
+"ForwardedHeadersOptions": {
+ "ForwardedHeaders": "All"
+}
+```
+
+### Force HTTPS scheme
+
+Some reverses proxies don't' forward headers. You can force HTTP requests schemes to https by settings ForceHttpsScheme.
+
+```json
+"ForceHttpsScheme": true
+```
+
+## Configure the provider hub
+
+The [Aguacongas.AspNetCore.Authentication library](https://github.com/Aguafrommars/DymamicAuthProviders) dynamically configures external providers.
+In a [load-balanced](https://github.com/Aguafrommars/DymamicAuthProviders/wiki/Load-balancing-scenario) configuration, the provider hub informs other running instances that an external provider configuration changes.
+The **SignalR** section defines the configuration for both the SignalR hub and the client.
+
+```json
+"SignalR": {
+ "HubUrl": "https://theidserverprivate/providerhub",
+ "HubOptions": {
+ "EnableDetailedErrors": true
+ },
+ "UseMessagePack": true,
+ "RedisConnectionString": "redis:6379",
+ "RedisOptions": {
+ "Configuration": {
+ "ChannelPrefix": "TheIdServer"
+ }
+ }
+}
+```
+
+If needed, the hub can use a [Redis backplane](https://docs.microsoft.com/en-us/aspnet/core/signalr/redis-backplane). **SignalR:RedisConnectionString** and **SignalR:RedisOptions** configures the backplane.
+**SignalR:RedisOptions** is bound to an instance of [`Microsoft.AspNetCore.SignalR.StackExchangeRedis.RedisOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.signalr.stackexchangeredis.redisoptions?view=aspnetcore-3.0) at startup.
+
+## Configure logs
+
+The **Serilog** section defines the [Serilog](https://serilog.net/) configuration.
+
+```json
+"Serilog": {
+ "LevelSwitches": {
+ "$controlSwitch": "Information"
+ },
+ "MinimumLevel": {
+ "ControlledBy": "$controlSwitch"
+ },
+ "WriteTo": [
+ {
+ "Name": "Seq",
+ "Args": {
+ "serverUrl": "http://localhost:5341",
+ "controlLevelSwitch": "$controlSwitch",
+ "apiKey": "DVYuookX2vOq078fuOyJ"
+ }
+ },
+ {
+ "Name": "Console",
+ "Args": {
+ "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}",
+ "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Literate, Serilog.Sinks.Console"
+ }
+ },
+ {
+ "Name": "Debug",
+ "Args": {
+ "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}"
+ }
+ }
+ ],
+ "Enrich": [
+ "FromLogContext",
+ "WithMachineName",
+ "WithThreadId"
+ ]
+}
+```
+For more details, read [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration/blob/dev/README.md).
+
+## Configure claims providers
+
+[Claims provider](CLAIMS_PROVIDER.md) provides details on claims proivder configuration.
+
+## Configure token cleaner
+
+The token cleaner task removes expired tokens periodically. To configure the interval, use **TokenCleanupInterval**.
+
+```json
+"TokenCleanupInterval": "00:05:00"
+```
+
+To disable the task, use **DisableTokenCleanup**.
+
+```json
+"DisableTokenCleanup": true
+```
+
+> The task is not enabled on proxy server.
+
+## Configure Dynamic client registration allowed contacts a host
+
+The server supports [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html).
+
+New client registration is allowed to users with the **Is4-Writer** role by sending the user access token or to contacts defined in *DynamicClientRegistrationOptions* section.
+
+```json
+"DynamicClientRegistrationOptions": {
+ "AllowedContacts": [
+ {
+ "Contact": "certification@oidf.org",
+ "AllowedHosts": [
+ "www.certification.openid.net"
+ ]
+ }
+ ]
+}
+```
+
+It this case, the client registration request must contain the *contacts* array.
+
+**request sample**
+
+```json
+{
+ "client_name": "oidc_cert_client gUPPBlHIEAqNOYR",
+ "grant_types": [
+ "authorization_code"
+ ],
+ "response_types": [
+ "code"
+ ],
+ "redirect_uris": [
+ "https://www.certification.openid.net/test/a/theidserver/callback"
+ ],
+ "contacts": [
+ "certification@oidf.org"
+ ]
+}
+```
+
+## Configure Jwt request validator
+
+Tokens returned by request_uri parameter are validated using the rules defined in *TokenValidationParameters* section. By default, the following rules are defined.
+
+```json
+"TokenValidationParameters": {
+ "ValidateIssuer": false,
+ "ValidateAudience": false,
+ "ValidateIssuerSigningKey": false,
+ "ValidateLifetime": false,
+ "RequireAudience": false,
+ "RequireExpirationTime": false,
+ "RequireSignedTokens": false
+}
+```
+
+> To enable JWT request uri, set *EnableJwtRequestUri* to true in *IdentityServerOptions:Endpoints*
+> ```json
+> "IdentityServerOptions": {
+> "Endpoints": {
+> "EnableJwtRequestUri": true
+> }
+> },
+> ```
+
+## Configure WS-Federation endpoint
+
+Read [Aguacongas.IdentityServer.WsFederation.Duende](../IdentityServer/Duende/Aguacongas.IdentityServer.WsFederation.Duende/README.md)
+
+## Configure CIBA notification service
+
+Read [DUENDE CIBA INTEGRATION/Notification service](CIBA.md#Notification-service)
+
+## Use the client to override the default configuration
+
+The server and the blazor app integrate [Aguafrommars/DynamicConfiguration](https://github.com/Aguafrommars/DynamicConfiguration). Most of the configuration can be ovveriden using the blazor app.
+
+Use **DynamicConfigurationOptions** to define the dynamic configuration provider.
+
+```json
+"DynamicConfigurationOptions": {
+ "ProviderType": "Aguacongas.DynamicConfiguration.Redis.RedisConfigurationProvider, Aguacongas.DynamicConfiguration.Redis"
+}
+```
+Use **RedisConfigurationOptions** section to configure the Redis db.
+
+```json
+"RedisConfigurationOptions": {
+ "ConnectionString": "localhost",
+ "HashKey": "Aguacongas.TheIdServer.Duende",
+ "Channel": "Aguacongas.TheIdServer.Duende.Channel"
+}
+```
+
+## Health checks
+
+The server expose an health checks enpoint you can use for docker on kubernetes at **/healthz**.
+
+The endpoit return a json reponse depending on the store kind used and redis dependencies :
+
+```json
+{
+ "status": "Healthy",
+ "results": {
+ "ConfigurationDbContext": {
+ "status": "Healthy"
+ },
+ "OperationalDbContext": {
+ "status": "Healthy"
+ },
+ "ApplicationDbContext": {
+ "status": "Healthy"
+ },
+ "DynamicConfigurationRedis": {
+ "status": "Healthy"
+ }
+ }
+}
+```
+
+## Configure OpenTelemetry
+
+[Configure OpenTelemetry doc](OPEN_TELEMETRY.md) provides details on [OpenTelemetry](https://opentelemetry.io/) configuration.
+
+## Additional resources
+
+* [Host and deploy ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/)
+* [DymamicAuthProviders](https://github.com/Aguafrommars/DymamicAuthProviders)
+* [Set up a Redis backplane for ASP.NET Core SignalR scale-out](https://docs.microsoft.com/en-us/aspnet/core/signalr/redis-backplane)
+* [Microsoft.AspNetCore.SignalR.StackExchangeRedis.RedisOptions](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.signalr.stackexchangeredis.redisoptions?view=aspnetcore-3.0)
+* [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration/blob/dev/README.md)
+* [Hosting ASP.NET Core images with Docker over HTTPS](https://docs.microsoft.com/en-us/aspnet/core/security/docker-https)
+* [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html)
+* [Aguafrommars/DynamicConfiguration](https://github.com/Aguafrommars/DynamicConfiguration)
+* [OpenTelemetry](https://opentelemetry.io/)
diff --git a/doc/SETUP.md b/doc/SETUP.md
new file mode 100644
index 000000000..68d28dda4
--- /dev/null
+++ b/doc/SETUP.md
@@ -0,0 +1,9 @@
+# SETUP
+
+## Server
+
+Setup instructions are discribed in [TheIdServer Duende Web Server project](../src/Aguacongas.TheIdServer.Duende/README.md).
+
+## Standalone admin app
+
+Setup instructions are discribed in [TheIdServer Admin Application](../src/Aguacongas.TheIdServer.BlazorApp/README.md)
\ No newline at end of file
diff --git a/sample/Aguacongas.TheIdServer.ApiSample/Aguacongas.TheIdServer.ApiSample.csproj b/sample/Aguacongas.TheIdServer.ApiSample/Aguacongas.TheIdServer.ApiSample.csproj
index f236d879f..dc260d379 100644
--- a/sample/Aguacongas.TheIdServer.ApiSample/Aguacongas.TheIdServer.ApiSample.csproj
+++ b/sample/Aguacongas.TheIdServer.ApiSample/Aguacongas.TheIdServer.ApiSample.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
@@ -13,9 +13,9 @@
-
-
-
+
+
+
diff --git a/sample/Aguacongas.TheIdServer.CibaSample/Aguacongas.TheIdServer.CibaSample.csproj b/sample/Aguacongas.TheIdServer.CibaSample/Aguacongas.TheIdServer.CibaSample.csproj
index 198fb6692..2588ecca6 100644
--- a/sample/Aguacongas.TheIdServer.CibaSample/Aguacongas.TheIdServer.CibaSample.csproj
+++ b/sample/Aguacongas.TheIdServer.CibaSample/Aguacongas.TheIdServer.CibaSample.csproj
@@ -2,7 +2,7 @@
Exe
- net7.0
+ net8.0enableenablefalse
diff --git a/sample/Aguacongas.TheIdServer.ClientCredentialSample/Aguacongas.TheIdServer.ClientCredentialSample.csproj b/sample/Aguacongas.TheIdServer.ClientCredentialSample/Aguacongas.TheIdServer.ClientCredentialSample.csproj
index df940b4f9..92efdb381 100644
--- a/sample/Aguacongas.TheIdServer.ClientCredentialSample/Aguacongas.TheIdServer.ClientCredentialSample.csproj
+++ b/sample/Aguacongas.TheIdServer.ClientCredentialSample/Aguacongas.TheIdServer.ClientCredentialSample.csproj
@@ -2,7 +2,7 @@
Exe
- net7.0
+ net8.0false
diff --git a/sample/Aguacongas.TheIdServer.CustomClaimsProvider/Aguacongas.TheIdServer.CustomClaimsProviders.csproj b/sample/Aguacongas.TheIdServer.CustomClaimsProvider/Aguacongas.TheIdServer.CustomClaimsProviders.csproj
index 1354352b2..7d4f7dc1b 100644
--- a/sample/Aguacongas.TheIdServer.CustomClaimsProvider/Aguacongas.TheIdServer.CustomClaimsProviders.csproj
+++ b/sample/Aguacongas.TheIdServer.CustomClaimsProvider/Aguacongas.TheIdServer.CustomClaimsProviders.csproj
@@ -1,14 +1,14 @@
- net7.0
+ net8.0falseTrue
-
-
+
+
diff --git a/sample/Aguacongas.TheIdServer.DeviceFlowSample/Aguacongas.TheIdServer.DeviceFlowSample.csproj b/sample/Aguacongas.TheIdServer.DeviceFlowSample/Aguacongas.TheIdServer.DeviceFlowSample.csproj
index a75e07773..71a021f51 100644
--- a/sample/Aguacongas.TheIdServer.DeviceFlowSample/Aguacongas.TheIdServer.DeviceFlowSample.csproj
+++ b/sample/Aguacongas.TheIdServer.DeviceFlowSample/Aguacongas.TheIdServer.DeviceFlowSample.csproj
@@ -2,7 +2,7 @@
Exe
- net7.0
+ net8.0false
diff --git a/sample/Aguacongas.TheIdServer.MvcClient/Aguacongas.TheIdServer.MvcClient.csproj b/sample/Aguacongas.TheIdServer.MvcClient/Aguacongas.TheIdServer.MvcClient.csproj
index e59bcb8a2..4c8189d58 100644
--- a/sample/Aguacongas.TheIdServer.MvcClient/Aguacongas.TheIdServer.MvcClient.csproj
+++ b/sample/Aguacongas.TheIdServer.MvcClient/Aguacongas.TheIdServer.MvcClient.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0false
@@ -15,9 +15,9 @@
-
-
-
+
+
+
diff --git a/sample/Aguacongas.TheIdServer.WsFederationSample/Aguacongas.TheIdServer.WsFederationSample.csproj b/sample/Aguacongas.TheIdServer.WsFederationSample/Aguacongas.TheIdServer.WsFederationSample.csproj
index 180a35961..e53310e45 100644
--- a/sample/Aguacongas.TheIdServer.WsFederationSample/Aguacongas.TheIdServer.WsFederationSample.csproj
+++ b/sample/Aguacongas.TheIdServer.WsFederationSample/Aguacongas.TheIdServer.WsFederationSample.csproj
@@ -1,14 +1,14 @@
- net7.0
+ net8.0false
-
-
-
+
+
+
diff --git a/sample/Aguacongas.TheIdentityServer.SpaSample/Aguacongas.TheIdentityServer.SpaSample.csproj b/sample/Aguacongas.TheIdentityServer.SpaSample/Aguacongas.TheIdentityServer.SpaSample.csproj
index a5a81bf08..563fcc24c 100644
--- a/sample/Aguacongas.TheIdentityServer.SpaSample/Aguacongas.TheIdentityServer.SpaSample.csproj
+++ b/sample/Aguacongas.TheIdentityServer.SpaSample/Aguacongas.TheIdentityServer.SpaSample.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
@@ -13,11 +13,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/sample/DPoP/Api/ApiHost.csproj b/sample/DPoP/Api/ApiHost.csproj
index bc690473c..30e1eee03 100644
--- a/sample/DPoP/Api/ApiHost.csproj
+++ b/sample/DPoP/Api/ApiHost.csproj
@@ -1,13 +1,13 @@
- net7.0
+ net8.0
-
-
+
+
\ No newline at end of file
diff --git a/sample/DPoP/Api/DPoP/DPoPJwtBearerEvents.cs b/sample/DPoP/Api/DPoP/DPoPJwtBearerEvents.cs
index 64bb8369e..a48b7a0d4 100644
--- a/sample/DPoP/Api/DPoP/DPoPJwtBearerEvents.cs
+++ b/sample/DPoP/Api/DPoP/DPoPJwtBearerEvents.cs
@@ -1,5 +1,6 @@
using IdentityModel;
using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using System.Text;
@@ -130,8 +131,7 @@ public override Task Challenge(JwtBearerChallengeContext context)
}
}
- context.Response.Headers.Add(HeaderNames.WWWAuthenticate, sb.ToString());
-
+ context.Response.Headers.Append(HeaderNames.WWWAuthenticate, sb.ToString());
if (context.HttpContext.Items.ContainsKey("DPoP-Nonce"))
{
diff --git a/sample/DPoP/Api/DPoP/DPoPProofValidator.cs b/sample/DPoP/Api/DPoP/DPoPProofValidator.cs
index 606a0eaef..7a5c473f3 100644
--- a/sample/DPoP/Api/DPoP/DPoPProofValidator.cs
+++ b/sample/DPoP/Api/DPoP/DPoPProofValidator.cs
@@ -169,7 +169,7 @@ protected virtual Task ValidateHeaderAsync(DPoPProofValidatonContext context, DP
///
/// Validates the signature.
///
- protected virtual Task ValidateSignatureAsync(DPoPProofValidatonContext context, DPoPProofValidatonResult result)
+ protected virtual async Task ValidateSignatureAsync(DPoPProofValidatonContext context, DPoPProofValidatonResult result)
{
TokenValidationResult tokenValidationResult;
@@ -185,14 +185,14 @@ protected virtual Task ValidateSignatureAsync(DPoPProofValidatonContext context,
};
var handler = new JsonWebTokenHandler();
- tokenValidationResult = handler.ValidateToken(context.ProofToken, tvp);
+ tokenValidationResult = await handler.ValidateTokenAsync(context.ProofToken, tvp).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.LogDebug("Error parsing DPoP token: {error}", ex.Message);
result.IsError = true;
result.ErrorDescription = "Invalid signature on DPoP token.";
- return Task.CompletedTask;
+ return;
}
if (tokenValidationResult.Exception != null)
@@ -200,12 +200,10 @@ protected virtual Task ValidateSignatureAsync(DPoPProofValidatonContext context,
Logger.LogDebug("Error parsing DPoP token: {error}", tokenValidationResult.Exception.Message);
result.IsError = true;
result.ErrorDescription = "Invalid signature on DPoP token.";
- return Task.CompletedTask;
+ return;
}
result.Payload = tokenValidationResult.Claims;
-
- return Task.CompletedTask;
}
///
diff --git a/sample/DPoP/ClientCredentials/ClientCredentials.csproj b/sample/DPoP/ClientCredentials/ClientCredentials.csproj
index 7dac37251..9a2fdd13c 100644
--- a/sample/DPoP/ClientCredentials/ClientCredentials.csproj
+++ b/sample/DPoP/ClientCredentials/ClientCredentials.csproj
@@ -1,13 +1,13 @@
- net7.0
+ net8.0enable
-
+
diff --git a/sample/DPoP/WebClient/WebClient.csproj b/sample/DPoP/WebClient/WebClient.csproj
index f6fe79667..a53912fb5 100644
--- a/sample/DPoP/WebClient/WebClient.csproj
+++ b/sample/DPoP/WebClient/WebClient.csproj
@@ -1,13 +1,13 @@
- net7.0
+ net8.0
-
-
+
+
\ No newline at end of file
diff --git a/sample/MultiTiers/Aguacongas.TheIdServer.Api/Aguacongas.TheIdServer.Api.csproj b/sample/MultiTiers/Aguacongas.TheIdServer.Api/Aguacongas.TheIdServer.Api.csproj
index 2cd5fb3f0..a6b70a396 100644
--- a/sample/MultiTiers/Aguacongas.TheIdServer.Api/Aguacongas.TheIdServer.Api.csproj
+++ b/sample/MultiTiers/Aguacongas.TheIdServer.Api/Aguacongas.TheIdServer.Api.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0false3d7ce8dc-f8b3-4d0a-967c-7d1aeead003eLinux
@@ -35,24 +35,24 @@
1701;1702;NU1603
-
-
-
-
-
-
-
+
+
+
+
+
+
+ allruntime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
diff --git a/sample/MultiTiers/Aguacongas.TheIdServer.Private/Aguacongas.TheIdServer.Private.csproj b/sample/MultiTiers/Aguacongas.TheIdServer.Private/Aguacongas.TheIdServer.Private.csproj
index 6496ddd39..2f45af37a 100644
--- a/sample/MultiTiers/Aguacongas.TheIdServer.Private/Aguacongas.TheIdServer.Private.csproj
+++ b/sample/MultiTiers/Aguacongas.TheIdServer.Private/Aguacongas.TheIdServer.Private.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0falseLinux..\..
@@ -23,36 +23,36 @@
1701;1702;NU1603
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ allruntime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
+
+
+
+
-
+
-
-
-
+
+
+
\ No newline at end of file
diff --git a/sample/MultiTiers/Aguacongas.TheIdServer.Private/Quickstart/Account/InvalidReturnUrlException.cs b/sample/MultiTiers/Aguacongas.TheIdServer.Private/Quickstart/Account/InvalidReturnUrlException.cs
index d4aa6f918..9a3efc905 100644
--- a/sample/MultiTiers/Aguacongas.TheIdServer.Private/Quickstart/Account/InvalidReturnUrlException.cs
+++ b/sample/MultiTiers/Aguacongas.TheIdServer.Private/Quickstart/Account/InvalidReturnUrlException.cs
@@ -26,10 +26,5 @@ public InvalidReturnUrlException(string message) : this(message, null)
public InvalidReturnUrlException(string message, Exception innerException) : base(message, innerException)
{
}
-
- protected InvalidReturnUrlException(SerializationInfo serializationInfo, StreamingContext streamingContext)
- : base(serializationInfo, streamingContext)
- {
- }
}
}
diff --git a/sample/MultiTiers/Aguacongas.TheIdServer.Public/Aguacongas.TheIdServer.Public.csproj b/sample/MultiTiers/Aguacongas.TheIdServer.Public/Aguacongas.TheIdServer.Public.csproj
index 79f30b9f8..de6d902fc 100644
--- a/sample/MultiTiers/Aguacongas.TheIdServer.Public/Aguacongas.TheIdServer.Public.csproj
+++ b/sample/MultiTiers/Aguacongas.TheIdServer.Public/Aguacongas.TheIdServer.Public.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0falseLinux..\..
@@ -16,32 +16,32 @@
Aguacongas.TheIdServer.Public.ruleset
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ allruntime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
diff --git a/sample/MultiTiers/Aguacongas.TheIdServer.Public/Quickstart/Account/InvalidReturnUrlException.cs b/sample/MultiTiers/Aguacongas.TheIdServer.Public/Quickstart/Account/InvalidReturnUrlException.cs
index d4aa6f918..1d4dd7acb 100644
--- a/sample/MultiTiers/Aguacongas.TheIdServer.Public/Quickstart/Account/InvalidReturnUrlException.cs
+++ b/sample/MultiTiers/Aguacongas.TheIdServer.Public/Quickstart/Account/InvalidReturnUrlException.cs
@@ -1,6 +1,7 @@
// Project: Aguafrommars/TheIdServer
// Copyright (c) 2023 @Olivier Lefebvre
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
namespace Duende.IdentityServer.Quickstart.UI
@@ -10,6 +11,7 @@ namespace Duende.IdentityServer.Quickstart.UI
///
///
[Serializable]
+ [SuppressMessage("Major Code Smell", "S3925:\"ISerializable\" should be implemented correctly", Justification = "Obsolete")]
public class InvalidReturnUrlException : Exception
{
///
@@ -26,10 +28,5 @@ public InvalidReturnUrlException(string message) : this(message, null)
public InvalidReturnUrlException(string message, Exception innerException) : base(message, innerException)
{
}
-
- protected InvalidReturnUrlException(SerializationInfo serializationInfo, StreamingContext streamingContext)
- : base(serializationInfo, streamingContext)
- {
- }
}
}
diff --git a/src/Aguacongas.TheIdServer.Authentication/Aguacongas.TheIdServer.Authentication.csproj b/src/Aguacongas.TheIdServer.Authentication/Aguacongas.TheIdServer.Authentication.csproj
index f54c20c51..28a9c7e13 100644
--- a/src/Aguacongas.TheIdServer.Authentication/Aguacongas.TheIdServer.Authentication.csproj
+++ b/src/Aguacongas.TheIdServer.Authentication/Aguacongas.TheIdServer.Authentication.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0Olivier LefebvreCopyright (c) 2023 @Olivier LefebvreApache-2.0
@@ -15,7 +15,7 @@
-
+
diff --git a/src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj b/src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj
index 0a475a8c8..c2a012668 100644
--- a/src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj
+++ b/src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0Olivier LefebvreCopyright (c) 2023 @Olivier LefebvreApache-2.0
@@ -15,8 +15,6 @@
trueenableenable
-
- true
@@ -35,8 +33,9 @@
-
-
+
+
+
@@ -63,31 +62,35 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Aguacongas.TheIdServer.BlazorApp/App.razor.cs b/src/Aguacongas.TheIdServer.BlazorApp/App.razor.cs
index aaea11976..1e3db11f7 100644
--- a/src/Aguacongas.TheIdServer.BlazorApp/App.razor.cs
+++ b/src/Aguacongas.TheIdServer.BlazorApp/App.razor.cs
@@ -1,6 +1,5 @@
// Project: Aguafrommars/TheIdServer
// Copyright (c) 2023 @Olivier Lefebvre
-using Aguacongas.TheIdServer.BlazorApp.Services;
using Microsoft.AspNetCore.Components.Routing;
using System.Reflection;
@@ -8,13 +7,13 @@ namespace Aguacongas.TheIdServer.BlazorApp
{
public partial class App
{
- private readonly List _lazyLoadedAssemblies = new List
+ private readonly List _lazyLoadedAssemblies = new()
{
typeof(Pages.Index).Assembly
};
- private readonly string[] _pageKindList = new []
- {
+ private readonly string[] _pageKindList =
+ [
"Api",
"ApiScope",
"Client",
@@ -29,7 +28,7 @@ public partial class App
"RelyingParties",
"RelyingParty",
"Settings"
- };
+ ];
protected override async Task OnInitializedAsync()
{
@@ -37,27 +36,27 @@ protected override async Task OnInitializedAsync()
await _themeService.InitAsync().ConfigureAwait(false);
}
- private Task OnNavigateAsync(NavigationContext args)
+ private Task OnNavigateAsync(NavigationContext args)
{
var path = args.Path.Split("/")[0];
if (path == "protectresource")
{
- return LoadAssemblyAsync("Aguacongas.TheIdServer.BlazorApp.Pages.Api.dll");
+ return LoadAssemblyAsync("Aguacongas.TheIdServer.BlazorApp.Pages.Api.wasm");
}
if (path == "identityresource")
{
- return LoadAssemblyAsync("Aguacongas.TheIdServer.BlazorApp.Pages.Identity.dll");
+ return LoadAssemblyAsync("Aguacongas.TheIdServer.BlazorApp.Pages.Identity.wasm");
}
var pageKind = _pageKindList.FirstOrDefault(k => path == $"{k.ToLower()}s");
if (pageKind != null)
{
- return LoadAssemblyAsync($"Aguacongas.TheIdServer.BlazorApp.Pages.{pageKind}s.dll");
+ return LoadAssemblyAsync($"Aguacongas.TheIdServer.BlazorApp.Pages.{pageKind}s.wasm");
}
- pageKind = _pageKindList.FirstOrDefault(k => path == k.ToLower());
- return pageKind != null ? LoadAssemblyAsync($"Aguacongas.TheIdServer.BlazorApp.Pages.{pageKind}.dll") : Task.CompletedTask;
+ pageKind = _pageKindList.FirstOrDefault(k => path.Equals(k, StringComparison.InvariantCultureIgnoreCase));
+ return pageKind != null ? LoadAssemblyAsync($"Aguacongas.TheIdServer.BlazorApp.Pages.{pageKind}.wasm") : Task.CompletedTask;
}
private async Task LoadAssemblyAsync(string assemblyName)
diff --git a/src/Aguacongas.TheIdServer.BlazorApp/Dockerfile b/src/Aguacongas.TheIdServer.BlazorApp/Dockerfile
index 4981a5127..89ba6886a 100644
--- a/src/Aguacongas.TheIdServer.BlazorApp/Dockerfile
+++ b/src/Aguacongas.TheIdServer.BlazorApp/Dockerfile
@@ -1,7 +1,8 @@
ARG GITHIB_FEED_TOKEN
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
-FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["nuget.config", "."]
COPY ["src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj", "src/Aguacongas.TheIdServer.BlazorApp/"]
@@ -29,16 +30,16 @@ COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Users/Aguacongas.The
COPY ["src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/Aguacongas.IdentityServer.Admin.Http.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/"]
COPY ["src/IdentityServer/Aguacongas.IdentityServer.Store/Aguacongas.IdentityServer.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Store/"]
COPY ["src/Aguacongas.TheIdServer.BlazorApp/nginx.conf", "src/Aguacongas.TheIdServer.BlazorApp/"]
-RUN apt-get update -y
-RUN apt-get install -y python3
+RUN apt-get update -y && apt-get install -y python3 gcc && apt-get clean
RUN dotnet workload install wasm-tools
-RUN dotnet restore "src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj"
+RUN dotnet restore "./src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj"
COPY . .
WORKDIR "/src/src/Aguacongas.TheIdServer.BlazorApp"
-RUN dotnet build "Aguacongas.TheIdServer.BlazorApp.csproj" -c Release -o /app/build -p:FileVersion=$FILE_VERSION -p:SourceRevisionId=$SOURCE_VERSION
+RUN dotnet build "./Aguacongas.TheIdServer.BlazorApp.csproj" -c "$BUILD_CONFIGURATION" -o /app/build -p:FileVersion="$FILE_VERSION" -p:SourceRevisionId="$SOURCE_VERSION"
FROM build AS publish
-RUN dotnet publish "Aguacongas.TheIdServer.BlazorApp.csproj" -c Release -o /app/publish -p:FileVersion=$FILE_VERSION -p:SourceRevisionId=$SOURCE_VERSION
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "./Aguacongas.TheIdServer.BlazorApp.csproj" -c "$BUILD_CONFIGURATION" -o /app/publish /p:UseAppHost=false -p:FileVersion="$FILE_VERSION" -p:SourceRevisionId="$SOURCE_VERSION"
FROM nginx:1.25.3 AS final
WORKDIR /usr/share/nginx/html
diff --git a/src/Aguacongas.TheIdServer.BlazorApp/README.md b/src/Aguacongas.TheIdServer.BlazorApp/README.md
index ab0f5abe5..6f8481959 100644
--- a/src/Aguacongas.TheIdServer.BlazorApp/README.md
+++ b/src/Aguacongas.TheIdServer.BlazorApp/README.md
@@ -2,178 +2,4 @@
This project is the [Blazor Web Assembly](https//blazor.net) application to manage a TheIdServer instance.
-## Installation
-### From Docker
-
-The application is embedded in the [server's Linux image](../Aguacongas.TheIdServer/README.md#from-docker).
-If you prefer, you can install the [standalone application'sLinux image](https://hub.docker.com/r/aguacongas/theidserverapp).
-This image uses an [nginx](http://nginx.org/) server to host the application.
-
-### From Github Release
-
-The application is embedded in the [server's Github release](../Aguacongas.TheIdServer/README.md#from-github-release).
-You can choose to install the standalone application by selecting *Aguacongas.TheIdServer.BlazorApp{version}.zip* in the [list of releases](https://github.com/Aguafrommars/TheIdServer/releases).
-Unzip in the destination of your choice, and use the server of your choice.
-
-Read [Host and deploy ASP.NET Core Blazor WebAssembly](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/blazor/webassembly?view=aspnetcore-3.1) for more information.
-
-### From NuGet packages
-
-NuGet packages composing the application are available on [nuget.org](https://www.nuget.org/):
-
-* **Aguacongas.TheIdServer.BlazorApp.Infrastructure** contains application models, services, validators and extensions
-* **Aguacongas.TheIdServer.BlazorApp.Components** contains application components
-* **Aguacongas.TheIdServer.BlazorApp.Pages.*** contains application pages
-
-## Configuration
-
-The application obtains its configuration from appsettings.json and the environment-specific settings from appsettings.{environment}.json.
-
-**appsettings.json**
-
-```json
-{
- "administratorEmail": "aguacongas@gmail.com",
- "apiBaseUrl": "https://localhost:5443/api",
- "authenticationPaths": {
- "remoteRegisterPath": "/identity/account/register",
- "remoteProfilePath": "/identity/account/manage"
- },
- "loggingOptions": {
- "minimum": "Debug",
- "filters": [
- {
- "category": "System",
- "level": "Warning"
- },
- {
- "category": "Microsoft",
- "level": "Information"
- }
- ]
- },
- "userOptions": {
- "roleClaim": "role"
- },
- "providerOptions": {
- "authority": "https://localhost:5443/",
- "clientId": "theidserveradmin",
- "defaultScopes": [
- "openid",
- "profile",
- "theidserveradminapi"
- ],
- "postLogoutRedirectUri": "https://localhost:5443/authentication/logout-callback",
- "redirectUri": "https://localhost:5443/authentication/login-callback",
- "responseType": "code"
- },
- "settingsOptions": {
- "typeName": "Aguacongas.TheIdServer.BlazorApp.Models.ServerConfig, Aguacongas.TheIdServer.BlazorApp.Infrastructure",
- "apiUrl": "https://localhost:5443/api/api/configuration"
- },
- "menuOptions": {
- "showSettings": true
- },
- "welcomeContenUrl": "https://localhost:5443/welcome-fragment.html",
- "serverSideSessionEnabled": false,
- "cibaEnabled": false
-}
-```
-
-For more details, read [ASP.NET Core Blazor hosting model configuration / Blazor WebAssembly / Configuration](https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-model-configuration?view=aspnetcore-3.1#configuration).
-
-### apiBaseUrl
-
-Defines the URL to the API.
-
-### administratorEmail
-
-Defines the administrator eMail address.
-
-### authenticationPaths
-
-The section **authenticationPaths** is binded to the class `Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticationApplicationPathsOptions`.
-The application doesn't contain pages to register a new user or manage the current user, so we set the **authenticationPaths:remoteRegisterPath** and **authenticationPaths:remoteProfilePath** with their corresponding URL on the identity server.
-
- For more information, read [ASP.NET Core Blazor WebAssembly additional security scenarios / Customize app routes](https://docs.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/additional-scenarios?view=aspnetcore-3.1#customize-app-routes).
-
-### loggingOptions
-
-Defines logging options.
-
-#### minimum
-
-Defines the [log minimum level](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-3.1).
-
-#### filters
-
-Each item in this array adds a log filter by category and [LogLevel](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-3.1).
-
-### userOptions
-
-The section **userOptions** is bound to the class `Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticationUserOptions`.
-This configuration defines how users are authorized. The application and the API share the same authorization policy.
-
-* **Is4-Writer** authorizes users in this role to write data.
-* **Is4-Reader** permits users in this role to read data.
-
-**userOptions:roleClaim** define the role claims type.
-
-### providerOptions
-
-The section **providerOptions** is binded to the class `Microsoft.AspNetCore.Components.WebAssembly.Authentication.OidcProviderOptions`.
-This configuration section defines the application authentication.
-
-For more details, read [Secure an ASP.NET Core Blazor WebAssembly standalone app with the Authentication library / Authentication service support](https://docs.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/standalone-with-authentication-library?view=aspnetcore-3.1#authentication-service-support).
-
-### welcomeContenUrl
-
-Defines the URL to the welcome page content.
-
-## Welcome page customization
-
-Except for its title, the home page displays contents read from `welcomeContenUrl` endpoint.
-
-This endpoint should return an HTML fragment.
-
-[**sample**](../Aguacongas.TheIdServer/wwwroot/welcome-fragment.html)
-
-```html
-
- Visit the github site for doc, source code and issue tracking.
-
-
- If you have trouble with login, disable Chromium cookies-without-same-site-must-be-secure flag.
-
- chrome://flags/#cookies-without-same-site-must-be-secure
-
- This site is running under a free heroku dyno without end-to-end https.
-
-
- You can sign-in with alice to have reader/writer access, or bob for a read-only access.
- The password is Pass123$.
-
-```
-
-## UI Options
-### Hide settings menu
-
-To hide the settings menu, unset **menuOptions:showSettings**.
-
-### Hide CIBA grant type
-
-If CIBA is not enabled you can hide the CIBA grant type by unsetting cibaEnabled options.
-
-### Hide coordinate lifetime with user session checkbox
-
-If server side sessions are not enable you can hide the coordinate lifetime with user session checkbox in client tokens section by unsetting serverSideSessionEnabled options.
-
-## Additional resources
-
-* [ASP.NET Core Blazor hosting model configuration](https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-model-configuration?view=aspnetcore-3.1#configuration)
-* [ASP.NET Core Blazor WebAssembly additional security scenarios](https://docs.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/additional-scenarios?view=aspnetcore-3.1#customize-app-routes)
-* [Secure an ASP.NET Core Blazor WebAssembly standalone app with the Authentication library](https://docs.microsoft.com/en-us/aspnet/core/security/blazor/webassembly/standalone-with-authentication-library?view=aspnetcore-3.1#authentication-service-support)
-* [LogLevel Enum](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-3.1)
+[Doc](../../doc/ADMINAPP.md)
\ No newline at end of file
diff --git a/src/Aguacongas.TheIdServer.BlazorApp/Roots.xml b/src/Aguacongas.TheIdServer.BlazorApp/Roots.xml
new file mode 100644
index 000000000..fd65d88e6
--- /dev/null
+++ b/src/Aguacongas.TheIdServer.BlazorApp/Roots.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku-duende.json b/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku-duende.json
index 11c72fca8..2d8b9a53f 100644
--- a/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku-duende.json
+++ b/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku-duende.json
@@ -14,7 +14,7 @@
]
},
"settingsOptions": {
- "apiUrl": "https://theidserver-duende.herokuapp.com/api/api/configuration"
+ "apiUrl": "https://theidserver-duende.herokuapp.com/api/configuration"
},
"providerOptions": {
"authority": "https://theidserver-duende.herokuapp.com/",
diff --git a/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku.json b/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku.json
index 9b0b8afe1..6efa69dc2 100644
--- a/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku.json
+++ b/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.heroku.json
@@ -14,7 +14,7 @@
]
},
"settingsOptions": {
- "apiUrl": "https://theidserver.herokuapp.com/api/api/configuration"
+ "apiUrl": "https://theidserver.herokuapp.com/api/configuration"
},
"providerOptions": {
"authority": "https://theidserver.herokuapp.com/",
diff --git a/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.json b/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.json
index 6287195a2..5141a3f76 100644
--- a/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.json
+++ b/src/Aguacongas.TheIdServer.BlazorApp/wwwroot/appsettings.json
@@ -27,7 +27,7 @@
"welcomeContenUrl": "https://localhost:5443/api/welcomefragment",
"settingsOptions": {
"typeName": "Aguacongas.TheIdServer.BlazorApp.Models.ServerConfig, Aguacongas.TheIdServer.BlazorApp.Infrastructure",
- "apiUrl": "https://localhost:5443/api/api/configuration"
+ "apiUrl": "https://localhost:5443/api/configuration"
},
"menuOptions": {
"showSettings": true
diff --git a/src/Aguacongas.TheIdServer.Duende/Aguacongas.TheIdServer.Duende.csproj b/src/Aguacongas.TheIdServer.Duende/Aguacongas.TheIdServer.Duende.csproj
index ceaa651e1..c68cc5f67 100644
--- a/src/Aguacongas.TheIdServer.Duende/Aguacongas.TheIdServer.Duende.csproj
+++ b/src/Aguacongas.TheIdServer.Duende/Aguacongas.TheIdServer.Duende.csproj
@@ -1,13 +1,15 @@
- net7.0
+ net8.051a7f0be-96e4-42d5-ad09-37e9adabfff6Linux..\..True
-
+ enable
+ enable
+ $(DefineConstants)TRACE
@@ -32,6 +34,7 @@
+
@@ -39,10 +42,15 @@
+
+
+
+
-
-
+
+
+
diff --git a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml
index e331ec798..0a412a59a 100644
--- a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml
+++ b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml
@@ -31,15 +31,15 @@ else
-
-
-
+
+
+
-
@@ -50,7 +50,7 @@ else
- @Localizer["Don't have access to your authenticator device? You can log in with a recovery code.", UrlHelper.Page("./LoginWithRecoveryCode", new { returnUrl = Model.ReturnUrl })]
+ @Localizer["Don't have access to your authenticator device? You can log in with a recovery code.", UrlHelper.Page("./LoginWithRecoveryCode", new { returnUrl = Model.ReturnUrl })!]
@section Scripts {
diff --git a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs
index 7e69b37e3..d55341583 100644
--- a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs
@@ -41,11 +41,11 @@ public LoginWith2faModel(SignInManager signInManager,
}
[BindProperty]
- public InputModel Input { get; set; }
+ public InputModel? Input { get; set; }
public bool RememberMe { get; set; }
- public string ReturnUrl { get; set; }
+ public string? ReturnUrl { get; set; }
public bool RedirectToReturnUrl { get; set; }
@@ -55,13 +55,13 @@ public class InputModel
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Text)]
[Display(Name = "Authenticator code")]
- public string TwoFactorCode { get; set; }
+ public string? TwoFactorCode { get; set; }
[Display(Name = "Remember this machine")]
public bool RememberMachine { get; set; }
}
- public async Task OnGetAsync(bool rememberMe, string returnUrl = null)
+ public async Task OnGetAsync(bool rememberMe, string? returnUrl = null)
{
// Ensure the user has gone through the username & password screen first
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
@@ -77,7 +77,7 @@ public async Task OnGetAsync(bool rememberMe, string returnUrl =
return Page();
}
- public async Task OnPostAsync(string userName, bool rememberMe, string returnUrl = null)
+ public async Task OnPostAsync(string userName, bool rememberMe, string? returnUrl = null)
{
if (!ModelState.IsValid)
{
@@ -92,7 +92,7 @@ public async Task OnPostAsync(string userName, bool rememberMe, s
throw new InvalidOperationException($"Unable to load two-factor authentication user.");
}
- var authenticatorCode = Input.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
+ var authenticatorCode = Input!.TwoFactorCode!.Replace(" ", string.Empty).Replace("-", string.Empty);
var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine);
diff --git a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Ciba.cshtml b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Ciba.cshtml
index 1379d9749..a7c1c783f 100644
--- a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Ciba.cshtml
+++ b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Ciba.cshtml
@@ -14,7 +14,7 @@ Copyright (c) 2023 @Olivier Lefebvre
@ViewData["Title"]
@Localizer["Below is the list of backchannel login requests awaiting your approbation."]
diff --git a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Grants.cshtml.cs b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Grants.cshtml.cs
index 448680b9a..2f03c2d31 100644
--- a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Grants.cshtml.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Grants.cshtml.cs
@@ -20,7 +20,7 @@ public class GrantsModel : PageModel
private readonly IResourceStore _resources;
private readonly IEventService _events;
- public IEnumerable Grants { get; set; }
+ public IEnumerable? Grants { get; set; }
public GrantsModel(IIdentityServerInteractionService interaction,
IClientStore clients,
@@ -80,14 +80,14 @@ private async Task BuildViewModelAsync()
public class GrantViewModel
{
- public string ClientId { get; set; }
- public string ClientName { get; set; }
- public string ClientUrl { get; set; }
- public string ClientLogoUrl { get; set; }
+ public string? ClientId { get; set; }
+ public string? ClientName { get; set; }
+ public string? ClientUrl { get; set; }
+ public string? ClientLogoUrl { get; set; }
public DateTime Created { get; set; }
public DateTime? Expires { get; set; }
- public IEnumerable IdentityGrantNames { get; set; }
- public IEnumerable ApiGrantNames { get; set; }
+ public IEnumerable? IdentityGrantNames { get; set; }
+ public IEnumerable? ApiGrantNames { get; set; }
}
}
}
diff --git a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Sessions.cshtml.cs b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Sessions.cshtml.cs
index 8b0883870..eec06a766 100644
--- a/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Sessions.cshtml.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Areas/Identity/Pages/Account/Manage/Sessions.cshtml.cs
@@ -11,12 +11,12 @@ namespace Aguacongas.TheIdServer.Areas.Identity.Pages.Account.Manage
{
public class SessionModel : PageModel
{
- public QueryResult Sessions { get; set; }
+ public QueryResult? Sessions { get; set; }
[BindProperty, Required]
- public string Id { get; set; }
+ public string? Id { get; set; }
[BindProperty, Required]
- public string Button { get; set; }
+ public string? Button { get; set; }
private readonly ISessionManagementService _sessionManagementService;
private readonly IClientStore _clients;
@@ -31,7 +31,7 @@ public async Task OnGetAsync()
{
Sessions = await _sessionManagementService.QuerySessionsAsync(new SessionQuery
{
- SubjectId = User.FindFirst("sub").Value
+ SubjectId = User.FindFirst("sub")!.Value
}).ConfigureAwait(false);
return Page();
@@ -50,12 +50,12 @@ await _sessionManagementService.RemoveSessionsAsync(new RemoveSessionsContext
}
public string GetActivePageClass(int index)
- => index == Sessions.CurrentPage ? "active" : string.Empty;
+ => index == Sessions!.CurrentPage ? "active" : string.Empty;
public string GetPreviousPageClass()
- => Sessions.HasPrevResults ? string.Empty : "disabled";
+ => Sessions!.HasPrevResults ? string.Empty : "disabled";
public string GetNextPageClass()
- => Sessions.HasNextResults ? string.Empty : "disabled";
+ => Sessions!.HasNextResults ? string.Empty : "disabled";
}
}
diff --git a/src/Aguacongas.TheIdServer.Duende/Config.cs b/src/Aguacongas.TheIdServer.Duende/Config.cs
index 58c70cdba..febaadde7 100644
--- a/src/Aguacongas.TheIdServer.Duende/Config.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Config.cs
@@ -2,16 +2,14 @@
// Copyright (c) 2023 @Olivier Lefebvre
using Duende.IdentityServer;
using Duende.IdentityServer.Models;
-using Microsoft.Extensions.Configuration;
-using System;
-using System.Collections.Generic;
-using System.Linq;
using Entity = Aguacongas.IdentityServer.Store.Entity;
namespace Aguacongas.TheIdServer
{
public static class Config
{
+ internal static string[] SeedPage { get; } = ["/seed"];
+
public static IEnumerable GetIdentityResources()
{
var profile = new IdentityResources.Profile();
diff --git a/src/Aguacongas.TheIdServer.Duende/Dockerfile b/src/Aguacongas.TheIdServer.Duende/Dockerfile
index a13a97436..92e166eea 100644
--- a/src/Aguacongas.TheIdServer.Duende/Dockerfile
+++ b/src/Aguacongas.TheIdServer.Duende/Dockerfile
@@ -1,74 +1,79 @@
-#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
-FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
+#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
+USER app
WORKDIR /app
EXPOSE 80
EXPOSE 443
-FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["nuget.config", "."]
COPY ["src/Aguacongas.TheIdServer.Duende/Aguacongas.TheIdServer.Duende.csproj", "src/Aguacongas.TheIdServer.Duende/"]
-COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Aguacongas.IdentityServer.Admin.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/"]
-COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.KeysRotation.Duende/Aguacongas.IdentityServer.KeysRotation.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.KeysRotation.Duende/"]
-COPY ["src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/Aguacongas.IdentityServer.KeysRotation.csproj", "src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/"]
-COPY ["src/IdentityServer/Aguacongas.IdentityServer.Admin/Aguacongas.IdentityServer.Admin.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Admin/"]
-COPY ["src/IdentityServer/Aguacongas.IdentityServer/Aguacongas.IdentityServer.csproj", "src/IdentityServer/Aguacongas.IdentityServer/"]
-COPY ["src/Aguacongas.TheIdServer.Authentication/Aguacongas.TheIdServer.Authentication.csproj", "src/Aguacongas.TheIdServer.Authentication/"]
-COPY ["src/IdentityServer/Aguacongas.IdentityServer.Store/Aguacongas.IdentityServer.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Store/"]
-COPY ["src/Aguacongas.TheIdServer.Identity/Aguacongas.TheIdServer.Identity.csproj", "src/Aguacongas.TheIdServer.Identity/"]
-COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Aguacongas.IdentityServer.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/"]
-COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende/"]
-COPY ["src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/Aguacongas.IdentityServer.EntityFramework.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/"]
-COPY ["src/Aguacongas.TheIdServer/Aguacongas.TheIdServer.csproj", "src/Aguacongas.TheIdServer/"]
-COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Aguacongas.TheIdServer.Migrations.Sqlite.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/"]
-COPY ["src/IdentityServer/Aguacongas.IdentityServer.Http.Store/Aguacongas.IdentityServer.Http.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Http.Store/"]
-COPY ["src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/Aguacongas.IdentityServer.Admin.Http.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/"]
-COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Aguacongas.TheIdServer.Migrations.SqlServer.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/"]
-COPY ["src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/Aguacongas.IdentityServer.MongoDb.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/"]
-COPY ["src/IdentityServer/Aguacongas.IdentityServer.WsFederation/Aguacongas.IdentityServer.WsFederation.csproj", "src/IdentityServer/Aguacongas.IdentityServer.WsFederation/"]
COPY ["src/Aguacongas.TheIdServer.BlazorApp/Aguacongas.TheIdServer.BlazorApp.csproj", "src/Aguacongas.TheIdServer.BlazorApp/"]
-COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes/"]
COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/Aguacongas.TheIdServer.BlazorApp.Components.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Components/"]
COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/Aguacongas.TheIdServer.BlazorApp.Infrastructure.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Infrastructure/"]
-COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties/"]
-COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identity/Aguacongas.TheIdServer.BlazorApp.Pages.Identity.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identity/"]
+COPY ["src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/Aguacongas.IdentityServer.Admin.Http.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Admin.Http.Store/"]
+COPY ["src/IdentityServer/Aguacongas.IdentityServer.Store/Aguacongas.IdentityServer.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Store/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScope/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Apis/Aguacongas.TheIdServer.BlazorApp.Pages.Apis.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Apis/"]
COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Api/Aguacongas.TheIdServer.BlazorApp.Pages.Api.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Api/"]
-COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Role/Aguacongas.TheIdServer.BlazorApp.Pages.Role.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Role/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Clients/Aguacongas.TheIdServer.BlazorApp.Pages.Clients.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Clients/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Aguacongas.TheIdServer.BlazorApp.Pages.Client.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Culture/Aguacongas.TheIdServer.BlazorApp.Pages.Culture.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Culture/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider/"]
COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identities/Aguacongas.TheIdServer.BlazorApp.Pages.Identities.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identities/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identity/Aguacongas.TheIdServer.BlazorApp.Pages.Identity.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Identity/"]
COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Import/Aguacongas.TheIdServer.BlazorApp.Pages.Import.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Import/"]
-COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProvider/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Keys/Aguacongas.TheIdServer.BlazorApp.Pages.Keys.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Keys/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParties/"]
COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.RelyingParty/"]
-COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ExternalProviders/"]
-COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages/Aguacongas.TheIdServer.BlazorApp.Pages.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages/"]
-COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.ApiScopes/"]
-COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Culture/Aguacongas.TheIdServer.BlazorApp.Pages.Culture.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Culture/"]
-COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Clients/Aguacongas.TheIdServer.BlazorApp.Pages.Clients.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Clients/"]
-COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Cultures/"]
-COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Apis/Aguacongas.TheIdServer.BlazorApp.Pages.Apis.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Apis/"]
COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Roles/Aguacongas.TheIdServer.BlazorApp.Pages.Roles.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Roles/"]
-COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/Aguacongas.TheIdServer.BlazorApp.Pages.Client.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Client/"]
-COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Keys/Aguacongas.TheIdServer.BlazorApp.Pages.Keys.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Keys/"]
-COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.User/Aguacongas.TheIdServer.BlazorApp.Pages.User.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.User/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Role/Aguacongas.TheIdServer.BlazorApp.Pages.Role.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Role/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Settings/Aguacongas.TheIdServer.BlazorApp.Pages.Settings.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Settings/"]
COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Users/Aguacongas.TheIdServer.BlazorApp.Pages.Users.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.Users/"]
-COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Oracle/Aguacongas.TheIdServer.Migrations.Oracle.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Oracle/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.User/Aguacongas.TheIdServer.BlazorApp.Pages.User.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages.User/"]
+COPY ["src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages/Aguacongas.TheIdServer.BlazorApp.Pages.csproj", "src/BlazorApp/Aguacongas.TheIdServer.BlazorApp.Pages/"]
+COPY ["src/Aguacongas.TheIdServer/Aguacongas.TheIdServer.csproj", "src/Aguacongas.TheIdServer/"]
+COPY ["src/Aguacongas.TheIdServer.Authentication/Aguacongas.TheIdServer.Authentication.csproj", "src/Aguacongas.TheIdServer.Authentication/"]
+COPY ["src/Aguacongas.TheIdServer.Identity/Aguacongas.TheIdServer.Identity.csproj", "src/Aguacongas.TheIdServer.Identity/"]
+COPY ["src/IdentityServer/Aguacongas.IdentityServer.Admin/Aguacongas.IdentityServer.Admin.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Admin/"]
+COPY ["src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/Aguacongas.IdentityServer.KeysRotation.csproj", "src/IdentityServer/Aguacongas.IdentityServer.KeysRotation/"]
+COPY ["src/IdentityServer/Aguacongas.IdentityServer/Aguacongas.IdentityServer.csproj", "src/IdentityServer/Aguacongas.IdentityServer/"]
+COPY ["src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/Aguacongas.IdentityServer.EntityFramework.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.EntityFramework.Store/"]
+COPY ["src/IdentityServer/Aguacongas.IdentityServer.Http.Store/Aguacongas.IdentityServer.Http.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.Http.Store/"]
+COPY ["src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/Aguacongas.IdentityServer.MongoDb.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.MongoDb.Store/"]
+COPY ["src/IdentityServer/Aguacongas.IdentityServer.RavenDb.Store/Aguacongas.IdentityServer.RavenDb.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.RavenDb.Store/"]
+COPY ["src/IdentityServer/Aguacongas.IdentityServer.WsFederation/Aguacongas.IdentityServer.WsFederation.csproj", "src/IdentityServer/Aguacongas.IdentityServer.WsFederation/"]
COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.MySql/Aguacongas.TheIdServer.Migrations.MySql.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.MySql/"]
+COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Oracle/Aguacongas.TheIdServer.Migrations.Oracle.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Oracle/"]
COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/Aguacongas.TheIdServer.Migrations.PostgreSQL.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.PostgreSQL/"]
-COPY ["src/IdentityServer/Aguacongas.IdentityServer.RavenDb.Store/Aguacongas.IdentityServer.RavenDb.Store.csproj", "src/IdentityServer/Aguacongas.IdentityServer.RavenDb.Store/"]
-COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/"]
+COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/Aguacongas.TheIdServer.Migrations.Sqlite.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.Sqlite/"]
+COPY ["src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/Aguacongas.TheIdServer.Migrations.SqlServer.csproj", "src/IdentityServer/Migrations/Aguacongas.TheIdServer.Migrations.SqlServer/"]
+COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/Aguacongas.IdentityServer.Admin.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.Admin.Duende/"]
+COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/Aguacongas.IdentityServer.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.Duende/"]
+COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.KeysRotation.Duende/Aguacongas.IdentityServer.KeysRotation.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.KeysRotation.Duende/"]
+COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.EntityFramework.Store.Duende/"]
COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.MongoDb.Store.Duende/"]
+COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.RavenDb.Store.Duende/"]
+COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.Saml2p.Duende/Aguacongas.IdentityServer.Saml2p.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.Saml2p.Duende/"]
COPY ["src/IdentityServer/Duende/Aguacongas.IdentityServer.WsFederation.Duende/Aguacongas.IdentityServer.WsFederation.Duende.csproj", "src/IdentityServer/Duende/Aguacongas.IdentityServer.WsFederation.Duende/"]
-RUN apt-get update -y
-RUN apt-get install -y python3
+RUN apt-get update -y && apt-get install -y python3 gcc && apt-get clean
RUN dotnet workload install wasm-tools
-RUN dotnet restore "src/Aguacongas.TheIdServer.Duende/Aguacongas.TheIdServer.Duende.csproj"
+RUN dotnet restore "./src/Aguacongas.TheIdServer.Duende/./Aguacongas.TheIdServer.Duende.csproj"
COPY . .
WORKDIR "/src/src/Aguacongas.TheIdServer.Duende"
-RUN dotnet build "Aguacongas.TheIdServer.Duende.csproj" -c Release -o /app/build -p:FileVersion=$FILE_VERSION -p:SourceRevisionId=$SOURCE_VERSION
+RUN dotnet build "./Aguacongas.TheIdServer.Duende.csproj" -c "$BUILD_CONFIGURATION" -o /app/build -p:FileVersion="$FILE_VERSION" -p:SourceRevisionId="$SOURCE_VERSION"
FROM build AS publish
-RUN dotnet publish "Aguacongas.TheIdServer.Duende.csproj" -c Release -o /app/publish -p:FileVersion=$FILE_VERSION -p:SourceRevisionId=$SOURCE_VERSION
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "./Aguacongas.TheIdServer.Duende.csproj" -c "$BUILD_CONFIGURATION" -o /app/publish /p:UseAppHost=false -p:FileVersion="$FILE_VERSION" -p:SourceRevisionId="$SOURCE_VERSION"
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
-ENTRYPOINT ["dotnet", "Aguacongas.TheIdServer.Duende.dll"]
+ENTRYPOINT ["dotnet", "Aguacongas.TheIdServer.Duende.dll"]
\ No newline at end of file
diff --git a/src/Aguacongas.TheIdServer.Duende/Extensions/ApplicationBuilderExtensions.cs b/src/Aguacongas.TheIdServer.Duende/Extensions/ApplicationBuilderExtensions.cs
index cefd9fac7..2c6b1677e 100644
--- a/src/Aguacongas.TheIdServer.Duende/Extensions/ApplicationBuilderExtensions.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Extensions/ApplicationBuilderExtensions.cs
@@ -7,45 +7,40 @@
using Aguacongas.TheIdServer;
using Aguacongas.TheIdServer.Admin.Hubs;
using Aguacongas.TheIdServer.Authentication;
+using Aguacongas.TheIdServer.BlazorApp;
using Aguacongas.TheIdServer.BlazorApp.Models;
using Aguacongas.TheIdServer.Data;
using Aguacongas.TheIdServer.Models;
using Aguacongas.TheIdServer.Options.OpenTelemetry;
-using Duende.IdentityServer.Hosting;
using Duende.IdentityServer.Configuration;
-using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
-using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Serilog;
-using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
-using System.IO;
-using System.Linq;
using System.Net;
+using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
-using System.Threading.Tasks;
-using System;
-using Microsoft.Extensions.Logging;
-using System.Security.Cryptography;
namespace Microsoft.AspNetCore.Builder
{
public static class ApplicationBuilderExtensions
{
+ private static JsonSerializerOptions _serializerOptions = new JsonSerializerOptions
+ {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ WriteIndented = true
+ };
+
public static IApplicationBuilder UseTheIdServer(this IApplicationBuilder app, IWebHostEnvironment environment, IConfiguration configuration)
{
var isProxy = configuration.GetValue("Proxy");
@@ -74,7 +69,8 @@ public static IApplicationBuilder UseTheIdServer(this IApplicationBuilder app, I
if (environment.IsDevelopment())
{
app.UseDeveloperExceptionPage()
- .UseMigrationsEndPoint();
+ .UseMigrationsEndPoint()
+ .UseWebAssemblyDebugging();
}
else
{
@@ -109,17 +105,19 @@ public static IApplicationBuilder UseTheIdServer(this IApplicationBuilder app, I
.UseIdentityServerAdminApi("/api", child =>
{
ConfigureAdminApiHandler(configuration, child);
- })
- .UseRouting();
+ });
- app.UseIdentityServer();
+ app.UseIdentityServer()
+ .UseRouting()
+ .UseAuthentication();
if (!isProxy)
{
- app.UseIdentityServerAdminAuthentication("/providerhub", JwtBearerDefaults.AuthenticationScheme);
+ app.UseIdentityServerAdminAuthentication("/providerhub");
}
- app.UseAuthorization()
+ app.UseIdentityServerAdminAuthentication()
+ .UseAuthorization()
.Use((context, next) =>
{
var service = context.RequestServices;
@@ -134,6 +132,7 @@ public static IApplicationBuilder UseTheIdServer(this IApplicationBuilder app, I
.UsePrometheus(configuration)
.UseEndpoints(endpoints =>
{
+ endpoints.MapAdminApiControllers();
endpoints.MapHealthChecks("healthz", new HealthCheckOptions
{
ResponseWriter = WriteHealtResponse
@@ -146,34 +145,60 @@ public static IApplicationBuilder UseTheIdServer(this IApplicationBuilder app, I
endpoints.MapHub("/providerhub");
}
endpoints.MapFallbackToPage("/_Host");
- })
- .LoadDynamicAuthenticationConfiguration();
+ endpoints.MapRazorComponents()
+ .AddInteractiveWebAssemblyRenderMode();
+ });
+
- return app;
+ return app.LoadDynamicAuthenticationConfiguration();
}
private static void ConfigureAdminApiHandler(IConfiguration configuration, IApplicationBuilder child)
{
if (configuration.GetValue("EnableOpenApiDoc"))
{
- child.UseOpenApi()
- .UseSwaggerUi3(options =>
- {
- var settings = configuration.GetSection("SwaggerUiSettings").Get();
- options.OAuth2Client = settings.OAuth2Client;
- });
+ ConfigureOpentApi(configuration, child);
}
var allowedOrigin = configuration.GetSection("CorsAllowedOrigin").Get>();
- if (allowedOrigin != null)
+ if (allowedOrigin is null)
+ {
+ return;
+ }
+
+ child.UseCors(configure =>
{
- child.UseCors(configure =>
+ configure.SetIsOriginAllowed(origin => allowedOrigin.Any(o => o == origin))
+ .AllowAnyMethod()
+ .AllowAnyHeader()
+ .AllowCredentials();
+ });
+ }
+
+ private static void ConfigureOpentApi(IConfiguration configuration, IApplicationBuilder child)
+ {
+ child.UseOpenApi(
+ options =>
+ {
+ var settings = configuration.GetSection("SwaggerUiSettings").Get();
+ if (settings?.Path is not null)
+ {
+ var path = settings.Path;
+ path = path.EndsWith('/') ? path : $"{path}/";
+ options.Path = $"{settings.Path}{{documentName}}/swagger.json";
+ }
+ })
+ .UseSwaggerUi3(options =>
{
- configure.SetIsOriginAllowed(origin => allowedOrigin.Any(o => o == origin))
- .AllowAnyMethod()
- .AllowAnyHeader()
- .AllowCredentials();
+ var settings = configuration.GetSection("SwaggerUiSettings").Get();
+ options.OAuth2Client = settings?.OAuth2Client;
+ options.Path = settings?.Path;
+ if (settings?.Path is not null)
+ {
+ var path = settings.Path;
+ path = path.EndsWith('/') ? path : $"{path}/";
+ options.DocumentPath = $"{settings.Path}{{documentName}}/swagger.json";
+ }
});
- }
}
private static IApplicationBuilder UseClientCerificate(this IApplicationBuilder app, string certificateHeader)
@@ -194,7 +219,7 @@ private static IApplicationBuilder UseClientCerificate(this IApplicationBuilder
requestLogger.LogInformation("Get certificate from header {ClientCertificateHeader}", certificateHeader);
try
{
- context.Connection.ClientCertificate = X509Certificate2.CreateFromPem(Uri.UnescapeDataString(values[0]));
+ context.Connection.ClientCertificate = X509Certificate2.CreateFromPem(Uri.UnescapeDataString(values[0]!));
}
catch (CryptographicException e)
{
@@ -257,8 +282,8 @@ private static void ConfigureInitialData(IApplicationBuilder app, IConfiguration
var opContext = scope.ServiceProvider.GetRequiredService();
opContext.Database.Migrate();
- var appcontext = scope.ServiceProvider.GetService();
- appcontext.Database.Migrate();
+ var appContext = scope.ServiceProvider.GetRequiredService();
+ appContext.Database.Migrate();
}
if (configuration.GetValue("Seed"))
@@ -297,12 +322,6 @@ private static Task WriteHealtResponse(HttpContext context, HealthReport healthR
if (value.Data.Any())
{
- var serializerOptions = new JsonSerializerOptions
- {
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
- WriteIndented = true
- };
-
jsonWriter.WriteStartObject("data");
foreach (var item in value.Data)
@@ -310,7 +329,7 @@ private static Task WriteHealtResponse(HttpContext context, HealthReport healthR
jsonWriter.WritePropertyName(item.Key);
JsonSerializer.Serialize(jsonWriter, item.Value,
- item.Value?.GetType() ?? typeof(object), serializerOptions);
+ item.Value?.GetType() ?? typeof(object), _serializerOptions);
}
jsonWriter.WriteEndObject();
diff --git a/src/Aguacongas.TheIdServer.Duende/Extensions/IdentityServerBuilderExtensions.cs b/src/Aguacongas.TheIdServer.Duende/Extensions/IdentityServerBuilderExtensions.cs
index d86ca68dd..42635cbce 100644
--- a/src/Aguacongas.TheIdServer.Duende/Extensions/IdentityServerBuilderExtensions.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Extensions/IdentityServerBuilderExtensions.cs
@@ -5,13 +5,9 @@
using Aguacongas.IdentityServer.KeysRotation;
using Aguacongas.IdentityServer.KeysRotation.Extensions;
using Aguacongas.TheIdServer.Models;
-using Microsoft.Extensions.Configuration;
+using Duende.IdentityServer.Configuration;
using StackExchange.Redis;
-using System;
-using System.Collections.Generic;
-using System.IO;
using System.Security.Cryptography.X509Certificates;
-using Duende.IdentityServer.Configuration;
using static Duende.IdentityServer.IdentityServerConstants;
namespace Microsoft.Extensions.DependencyInjection
@@ -63,7 +59,7 @@ public static IIdentityServerBuilder ConfigureKey(this IIdentityServerBuilder id
additionalSigningKeyTypeSection.Bind(additionalKeyType);
foreach (var key in additionalKeyType.Keys)
{
- if (key.StartsWith("E"))
+ if (key.StartsWith('E'))
{
var ecdsa = Enum.Parse(key);
builder.AddECDsaKeysRotation(ecdsa, options => keyRotationSection?.Bind(options))
@@ -79,7 +75,7 @@ public static IIdentityServerBuilder ConfigureKey(this IIdentityServerBuilder id
}
var dataProtectionsOptions = configuration.Get();
- switch (dataProtectionsOptions.StorageKind)
+ switch (dataProtectionsOptions?.StorageKind)
{
case StorageKind.AzureStorage:
builder.PersistKeysToAzureBlobStorage(new Uri(dataProtectionsOptions.StorageConnectionString));
@@ -106,7 +102,7 @@ public static IIdentityServerBuilder ConfigureKey(this IIdentityServerBuilder id
builder.PersistKeysToStackExchangeRedis(redis, dataProtectionsOptions.RedisKey);
break;
}
- var protectOptions = dataProtectionsOptions.KeyProtectionOptions;
+ var protectOptions = dataProtectionsOptions?.KeyProtectionOptions;
if (protectOptions != null)
{
switch (protectOptions.KeyProtectionKind)
diff --git a/src/Aguacongas.TheIdServer.Duende/Extensions/ServiceCollectionExtensions.cs b/src/Aguacongas.TheIdServer.Duende/Extensions/ServiceCollectionExtensions.cs
index eaceef6e3..5cc885880 100644
--- a/src/Aguacongas.TheIdServer.Duende/Extensions/ServiceCollectionExtensions.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Extensions/ServiceCollectionExtensions.cs
@@ -11,39 +11,32 @@
using Aguacongas.TheIdServer.BlazorApp.Models;
using Aguacongas.TheIdServer.BlazorApp.Services;
using Aguacongas.TheIdServer.Data;
+using Aguacongas.TheIdServer.Identity.Argon2PasswordHasher;
+using Aguacongas.TheIdServer.Identity.BcryptPasswordHasher;
+using Aguacongas.TheIdServer.Identity.ScryptPasswordHasher;
+using Aguacongas.TheIdServer.Identity.UpgradePasswordHasher;
using Aguacongas.TheIdServer.Models;
using Aguacongas.TheIdServer.Services;
using Aguacongas.TheIdServer.UI;
using Duende.IdentityServer.Configuration;
-using Duende.IdentityServer.Configuration.Configuration;
using Duende.IdentityServer.Services;
using IdentityModel.AspNetCore.OAuth2Introspection;
using Microsoft.AspNetCore.Authentication.Certificate;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Components.WebAssembly.Services;
-using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
-using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Raven.Client.Documents;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
-using System.Threading.Tasks;
using ConfigurationModel = Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
namespace Microsoft.Extensions.DependencyInjection
@@ -60,13 +53,18 @@ public static IServiceCollection AddTheIdServer(this IServiceCollection services
.AddConfigurationStores()
.AddOperationalStores()
.AddTokenExchange()
+ .Configure(configurationManager.GetSection(nameof(PasswordHasherOptions)))
.AddIdentity(
options =>
{
configurationManager.Bind(nameof(AspNetCore.Identity.IdentityOptions), options);
})
.AddTheIdServerStores()
- .AddDefaultTokenProviders();
+ .AddDefaultTokenProviders()
+ .AddArgon2PasswordHasher(configurationManager.GetSection(nameof(Argon2PasswordHasherOptions)))
+ .AddBcryptPasswordHasher(configurationManager.GetSection(nameof(BcryptPasswordHasherOptions)))
+ .AddScryptPasswordHasher(configurationManager.GetSection(nameof(ScryptPasswordHasherOptions)))
+ .AddUpgradePasswordHasher(configurationManager.GetSection(nameof(UpgradePasswordHasherOptions)));
if (isProxy)
{
@@ -154,7 +152,7 @@ public static IServiceCollection AddTheIdServer(this IServiceCollection services
var authenticationBuilder = services.Configure(configurationManager.GetSection("Google"))
.AddAuthorization(options =>
- options.AddIdentityServerPolicies(true))
+ options.AddIdentityServerPolicies())
.AddAuthentication()
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => ConfigureIdentityServerJwtBearerOptions(options, configurationManager))
// reference tokens
@@ -164,7 +162,7 @@ public static IServiceCollection AddTheIdServer(this IServiceCollection services
if (mutulaTlsOptions?.Enabled == true)
{
// MutualTLS
- authenticationBuilder.AddCertificate(mutulaTlsOptions?.ClientCertificateAuthenticationScheme,
+ authenticationBuilder.AddCertificate(mutulaTlsOptions!.ClientCertificateAuthenticationScheme,
options => configurationManager.Bind(nameof(CertificateAuthenticationOptions), options));
}
@@ -207,7 +205,9 @@ public static IServiceCollection AddTheIdServer(this IServiceCollection services
.AddRazorPages(options => options.Conventions.AuthorizeAreaFolder("Identity", "/Account"));
ConfigureHealthChecks(services, dbType, isProxy, configurationManager);
-
+
+ services.AddRazorComponents().AddInteractiveWebAssemblyComponents();
+
return services;
}
@@ -394,12 +394,13 @@ private static void AddDefaultServices(IServiceCollection services, IConfigurati
}
}
- private static void ConfigureDataProtection(IServiceCollection services, IConfiguration configuration)
+ private static void ConfigureDataProtection(IServiceCollection services, ConfigurationManager configurationManager)
{
- var dataprotectionSection = configuration.GetSection(nameof(DataProtectionOptions));
- if (dataprotectionSection != null)
+ var dataProtectionSection = configurationManager.GetSection(nameof(DataProtectionOptions));
+ if (dataProtectionSection != null)
{
- services.AddDataProtection(options => dataprotectionSection.Bind(options)).ConfigureDataProtection(dataprotectionSection);
+ services.AddDataProtection(options => dataProtectionSection.Bind(options))
+ .ConfigureDataProtection(dataProtectionSection);
}
}
@@ -430,7 +431,7 @@ private static void ConfigureDbHealthChecks(DbTypes dbTypes, bool isProxy, IConf
switch (dbTypes)
{
case DbTypes.MongoDb:
- builder.AddMongoDb(configuration.GetConnectionString("DefaultConnection"), tags: tags);
+ builder.AddMongoDb(configuration.GetConnectionString("DefaultConnection")!, tags: tags);
break;
case DbTypes.RavenDb:
builder.AddRavenDB(options =>
diff --git a/src/Aguacongas.TheIdServer.Duende/Program.cs b/src/Aguacongas.TheIdServer.Duende/Program.cs
index 045e61bd9..8b273d639 100644
--- a/src/Aguacongas.TheIdServer.Duende/Program.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Program.cs
@@ -1,17 +1,11 @@
// Project: Aguafrommars/TheIdServer
// Copyright (c) 2023 @Olivier Lefebvre
using Aguacongas.TheIdServer;
-using Aguacongas.TheIdServer.BlazorApp.Models;
using Aguacongas.TheIdServer.Options.OpenTelemetry;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
using Serilog;
using System.Diagnostics;
-using System.Linq;
using MutualTlsOptions = Aguacongas.TheIdServer.BlazorApp.Models.MutualTlsOptions;
var builder = WebApplication.CreateBuilder(args);
@@ -28,7 +22,7 @@
var seed = args.Any(x => x == "/seed");
if (seed)
{
- args = args.Except(new[] { "/seed" }).ToArray();
+ args = args.Except(Config.SeedPage).ToArray();
}
services.AddOpenTelemetry(configuration.GetSection(nameof(OpenTelemetryOptions)));
@@ -61,4 +55,4 @@
});
app.UseTheIdServer(app.Environment, configuration);
-app.Run();
+await app.RunAsync().ConfigureAwait(false);
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/AccountController.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/AccountController.cs
index 333bbf314..18c6f0684 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/AccountController.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/AccountController.cs
@@ -1,25 +1,21 @@
// Project: Aguafrommars/TheIdServer
// Copyright (c) 2023 @Olivier Lefebvre
using Aguacongas.TheIdServer.Models;
-using IdentityModel;
using Duende.IdentityServer;
using Duende.IdentityServer.Events;
using Duende.IdentityServer.Extensions;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;
using Duende.IdentityServer.Stores;
+using IdentityModel;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
-using System;
using System.Diagnostics.CodeAnalysis;
-using System.Linq;
using System.Text.Encodings.Web;
-using System.Threading.Tasks;
namespace Aguacongas.TheIdServer.UI
{
@@ -91,20 +87,20 @@ public async Task Login(LoginInputModel model, string button)
// the user clicked the "cancel" button
if (button != "login")
{
- return await OnCancel(model, context).ConfigureAwait(false);
+ return await OnCancel(model, context!).ConfigureAwait(false);
}
if (ModelState.IsValid)
{
- var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberLogin, lockoutOnFailure: true).ConfigureAwait(false);
+ var result = await _signInManager.PasswordSignInAsync(model.Username!, model.Password!, model.RememberLogin, lockoutOnFailure: true).ConfigureAwait(false);
if (result.Succeeded)
{
- return await OnSiginSuccesss(model, context).ConfigureAwait(false);
+ return await OnSiginSuccesss(model, context!).ConfigureAwait(false);
}
if (result.RequiresTwoFactor)
{
- return Redirect($"/Identity/Account/LoginWith2fa?rememberMe={model.RememberLogin}&returnUrl={_urlEncoder.Encode(model.ReturnUrl)}");
+ return Redirect($"/Identity/Account/LoginWith2fa?rememberMe={model.RememberLogin}&returnUrl={_urlEncoder.Encode(model.ReturnUrl!)}");
}
await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials", clientId:context?.Client.ClientId)).ConfigureAwait(false);
@@ -118,8 +114,8 @@ public async Task Login(LoginInputModel model, string button)
private async Task OnSiginSuccesss(LoginInputModel model, AuthorizationRequest context)
{
- var user = await _userManager.FindByNameAsync(model.Username).ConfigureAwait(false);
- await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId)).ConfigureAwait(false);
+ var user = await _userManager.FindByNameAsync(model.Username!).ConfigureAwait(false);
+ await _events.RaiseAsync(new UserLoginSuccessEvent(user!.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId)).ConfigureAwait(false);
if (context != null)
{
@@ -131,7 +127,7 @@ private async Task OnSiginSuccesss(LoginInputModel model, Authori
}
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
- return Redirect(model.ReturnUrl);
+ return Redirect(model.ReturnUrl!);
}
// request for a local page
@@ -162,10 +158,10 @@ private async Task OnCancel(LoginInputModel model, AuthorizationR
{
// The client is native, so this change in how to
// return the response is for better UX for the end user.
- return this.LoadingPage("Redirect", model.ReturnUrl);
+ return this.LoadingPage("Redirect", model.ReturnUrl!);
}
- return Redirect(model.ReturnUrl);
+ return Redirect(model.ReturnUrl!);
}
else
{
@@ -202,9 +198,9 @@ public async Task Logout(string logoutId)
public async Task Logout(LogoutInputModel model)
{
// build a model so the logged out page knows what to display
- var vm = await BuildLoggedOutViewModelAsync(model.LogoutId).ConfigureAwait(false);
+ var vm = await BuildLoggedOutViewModelAsync(model.LogoutId!).ConfigureAwait(false);
- if (User?.Identity.IsAuthenticated == true)
+ if (User?.Identity?.IsAuthenticated == true)
{
// delete local authentication cookie
await _signInManager.SignOutAsync().ConfigureAwait(false);
@@ -219,10 +215,10 @@ public async Task Logout(LogoutInputModel model)
// build a return URL so the upstream provider will redirect back
// to us after the user has logged out. this allows us to then
// complete our single sign-out processing.
- string url = Url.Action("Logout", new { logoutId = vm.LogoutId });
+ var url = Url.Action("Logout", new { logoutId = vm.LogoutId });
// this triggers a redirect to the external provider for sign-out
- return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
+ return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme!);
}
return View("LoggedOut", vm);
@@ -251,7 +247,7 @@ private async Task BuildLoginViewModelAsync(string returnUrl)
{
EnableLocalLogin = local,
ReturnUrl = returnUrl,
- Username = context?.LoginHint,
+ Username = context.LoginHint,
};
if (!local)
@@ -283,9 +279,9 @@ private async Task BuildLoginViewModelAsync(string returnUrl)
{
allowLocal = client.EnableLocalLogin;
- if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any())
+ if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Count > 0)
{
- providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList();
+ providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme!)).ToList();
}
}
}
@@ -305,7 +301,7 @@ private async Task BuildLoginViewModelAsync(string returnUrl)
private async Task BuildLoginViewModelAsync(LoginInputModel model)
{
- var vm = await BuildLoginViewModelAsync(model.ReturnUrl).ConfigureAwait(false);
+ var vm = await BuildLoginViewModelAsync(model.ReturnUrl!).ConfigureAwait(false);
vm.Username = model.Username;
vm.RememberLogin = model.RememberLogin;
return vm;
@@ -315,7 +311,7 @@ private async Task BuildLogoutViewModelAsync(string logoutId)
{
var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = _options.Value.ShowLogoutPrompt };
- if (User?.Identity.IsAuthenticated != true)
+ if (User?.Identity?.IsAuthenticated != true)
{
// if the user is not authenticated, then just show logged out page
vm.ShowLogoutPrompt = false;
@@ -349,7 +345,7 @@ private async Task BuildLoggedOutViewModelAsync(string logou
LogoutId = logoutId
};
- if (User?.Identity.IsAuthenticated == true)
+ if (User?.Identity?.IsAuthenticated == true)
{
var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
if (idp != null && idp != IdentityServerConstants.LocalIdentityProvider)
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalController.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalController.cs
index 0dcc742c7..27c0bacaf 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalController.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalController.cs
@@ -1,23 +1,16 @@
// Project: Aguafrommars/TheIdServer
// Copyright (c) 2023 @Olivier Lefebvre
using Aguacongas.TheIdServer.Models;
-using IdentityModel;
using Duende.IdentityServer;
using Duende.IdentityServer.Events;
using Duende.IdentityServer.Services;
-using Duende.IdentityServer.Stores;
+using IdentityModel;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
-using System.Linq;
using System.Security.Claims;
-using System.Threading.Tasks;
namespace Aguacongas.TheIdServer.UI
{
@@ -101,7 +94,7 @@ public async Task Callback()
if (_logger.IsEnabled(LogLevel.Debug))
{
- var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}");
+ var externalClaims = result.Principal!.Claims.Select(c => $"{c.Type}: {c.Value}");
_logger.LogDebug("External claims: {@claims}", externalClaims);
}
@@ -112,7 +105,7 @@ public async Task Callback()
// this might be where you might initiate a custom workflow for user registration
// in this sample we don't show how that would be done, as our sample implementation
// simply auto-provisions new external user
- user = await AutoProvisionUserAsync(provider, providerUserId, claims).ConfigureAwait(false);
+ user = await AutoProvisionUserAsync(provider!, providerUserId, claims).ConfigureAwait(false);
}
// this allows us to collect any additonal claims or properties
@@ -141,7 +134,7 @@ public async Task Callback()
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
// retrieve return URL
- var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";
+ var returnUrl = result.Properties!.Items["returnUrl"] ?? "~/";
// check if external login is in the context of an OIDC request
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
@@ -172,28 +165,28 @@ private async Task ProcessWindowsLoginAsync(string provider, Clai
};
var id = new ClaimsIdentity(provider);
- var name = user.Identity.Name ??
+ var name = user.Identity!.Name ??
user.FindFirst(JwtClaimTypes.Name)?.Value ??
user.FindFirst(ClaimTypes.Name)?.Value ??
user.FindFirst(JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap[ClaimTypes.Name])?.Value ??
user.FindFirst(ClaimTypes.NameIdentifier)?.Value ??
user.FindFirst(JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap[ClaimTypes.NameIdentifier])?.Value;
- id.AddClaim(new Claim(JwtClaimTypes.Subject, name));
- id.AddClaim(new Claim(JwtClaimTypes.Name, name));
+ id.AddClaim(new Claim(JwtClaimTypes.Subject, name!));
+ id.AddClaim(new Claim(JwtClaimTypes.Name, name!));
await HttpContext.SignInAsync(
IdentityConstants.ExternalScheme,
new ClaimsPrincipal(id),
props).ConfigureAwait(false);
- return Redirect(props.RedirectUri);
+ return Redirect(props.RedirectUri!);
}
- private async Task<(ApplicationUser user, string provider, string providerUserId, IEnumerable claims)>
+ private async Task<(ApplicationUser? user, string? provider, string providerUserId, IEnumerable claims)>
FindUserFromExternalProviderAsync(AuthenticateResult result)
{
- var externalUser = result.Principal;
+ var externalUser = result.Principal!;
// try to determine the unique id of the external user (issued by the provider)
// the most common claim type for that are the sub claim and the NameIdentifier
@@ -207,11 +200,11 @@ await HttpContext.SignInAsync(
var claims = externalUser.Claims.ToList();
claims.Remove(userIdClaim);
- var provider = result.Properties.Items["scheme"];
+ var provider = result.Properties?.Items["scheme"];
var providerUserId = userIdClaim.Value;
// find external user
- var user = await _userManager.FindByLoginAsync(provider, providerUserId);
+ var user = await _userManager.FindByLoginAsync(provider!, providerUserId);
return (user, provider, providerUserId, claims);
}
@@ -263,7 +256,7 @@ private async Task AutoProvisionUserAsync(string provider, stri
var identityResult = await _userManager.CreateAsync(user);
if (!identityResult.Succeeded) throw new InvalidOperationException(identityResult.Errors.First().Description);
- if (filtered.Any())
+ if (filtered.Count > 0)
{
identityResult = await _userManager.AddClaimsAsync(user, filtered);
if (!identityResult.Succeeded) throw new InvalidOperationException(identityResult.Errors.First().Description);
@@ -280,14 +273,14 @@ private static void ProcessLoginCallbackForOidc(AuthenticateResult externalResul
{
// if the external system sent a session id claim, copy it over
// so we can use it for single sign-out
- var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId);
+ var sid = externalResult.Principal!.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId);
if (sid != null)
{
localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
}
// if the external provider issued an id_token, we'll keep it for signout
- var id_token = externalResult.Properties.GetTokenValue("id_token");
+ var id_token = externalResult.Properties!.GetTokenValue("id_token");
if (id_token != null)
{
localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = id_token } });
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalProvider.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalProvider.cs
index bbb94f9b4..df5df603b 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalProvider.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/ExternalProvider.cs
@@ -4,7 +4,7 @@ namespace Aguacongas.TheIdServer.UI
{
public class ExternalProvider
{
- public string DisplayName { get; set; }
- public string AuthenticationScheme { get; set; }
+ public string? DisplayName { get; set; }
+ public string? AuthenticationScheme { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/InvalidReturnUrlException.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/InvalidReturnUrlException.cs
index ac19934f1..c88b720cf 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/InvalidReturnUrlException.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/InvalidReturnUrlException.cs
@@ -1,6 +1,7 @@
// Project: Aguafrommars/TheIdServer
// Copyright (c) 2023 @Olivier Lefebvre
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
namespace Aguacongas.TheIdServer.UI
@@ -10,6 +11,7 @@ namespace Aguacongas.TheIdServer.UI
///
///
[Serializable]
+ [SuppressMessage("Major Code Smell", "S3925:\"ISerializable\" should be implemented correctly", Justification = "Obsolete")]
public class InvalidReturnUrlException : Exception
{
///
@@ -23,12 +25,7 @@ public InvalidReturnUrlException(string message) : this(message, null)
{
}
- public InvalidReturnUrlException(string message, Exception innerException) : base(message, innerException)
- {
- }
-
- protected InvalidReturnUrlException(SerializationInfo serializationInfo, StreamingContext streamingContext)
- : base(serializationInfo, streamingContext)
+ public InvalidReturnUrlException(string message, Exception? innerException) : base(message, innerException)
{
}
}
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoggedOutViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoggedOutViewModel.cs
index 5513cf399..fe1552c2e 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoggedOutViewModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoggedOutViewModel.cs
@@ -4,14 +4,14 @@ namespace Aguacongas.TheIdServer.UI
{
public class LoggedOutViewModel
{
- public string PostLogoutRedirectUri { get; set; }
- public string ClientName { get; set; }
- public string SignOutIframeUrl { get; set; }
+ public string? PostLogoutRedirectUri { get; set; }
+ public string? ClientName { get; set; }
+ public string? SignOutIframeUrl { get; set; }
public bool AutomaticRedirectAfterSignOut { get; set; }
- public string LogoutId { get; set; }
+ public string? LogoutId { get; set; }
public bool TriggerExternalSignout => ExternalAuthenticationScheme != null;
- public string ExternalAuthenticationScheme { get; set; }
+ public string? ExternalAuthenticationScheme { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginInputModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginInputModel.cs
index 7b9374be8..018eb6b44 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginInputModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginInputModel.cs
@@ -7,10 +7,10 @@ namespace Aguacongas.TheIdServer.UI
public class LoginInputModel
{
[Required]
- public string Username { get; set; }
+ public string? Username { get; set; }
[Required]
- public string Password { get; set; }
+ public string? Password { get; set; }
public bool RememberLogin { get; set; }
- public string ReturnUrl { get; set; }
+ public string? ReturnUrl { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginViewModel.cs
index 619547808..e5f7eb034 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginViewModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LoginViewModel.cs
@@ -11,11 +11,11 @@ public class LoginViewModel : LoginInputModel
public bool AllowRememberLogin { get; set; } = true;
public bool EnableLocalLogin { get; set; } = true;
- public IEnumerable ExternalProviders { get; set; } = Enumerable.Empty();
- public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !string.IsNullOrWhiteSpace(x.DisplayName));
+ public IEnumerable? ExternalProviders { get; set; } = Enumerable.Empty();
+ public IEnumerable? VisibleExternalProviders => ExternalProviders?.Where(x => !string.IsNullOrWhiteSpace(x.DisplayName));
public bool IsExternalLoginOnly => !EnableLocalLogin && ExternalProviders?.Count() == 1;
- public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null;
+ public string? ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null;
public bool ShowForgotPassworLink { get; set; } = true;
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LogoutInputModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LogoutInputModel.cs
index a01d51cef..5ae004d48 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LogoutInputModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/LogoutInputModel.cs
@@ -4,6 +4,6 @@ namespace Aguacongas.TheIdServer.UI
{
public class LogoutInputModel
{
- public string LogoutId { get; set; }
+ public string? LogoutId { get; set; }
}
}
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/RedirectViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/RedirectViewModel.cs
index ded169e58..a6d180e2e 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/RedirectViewModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Account/RedirectViewModel.cs
@@ -4,6 +4,6 @@ namespace Aguacongas.TheIdServer.UI
{
public class RedirectViewModel
{
- public string RedirectUrl { get; set; }
+ public string? RedirectUrl { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/CibaController.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/CibaController.cs
index bf455cc69..3b0618797 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/CibaController.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/CibaController.cs
@@ -35,7 +35,7 @@ public CibaController(IBackchannelAuthenticationInteractionService interaction,
public async Task Consent(string id)
{
var request = await _interaction.GetLoginRequestByInternalIdAsync(id);
- var model = BuildViewModelAsync(request, id);
+ var model = BuildViewModelAsync(request!, id);
return View(model);
}
@@ -44,18 +44,18 @@ public async Task Consent(string id)
[ValidateAntiForgeryToken]
public async Task Consent([FromForm] InputModel input)
{
- var request = await _interaction.GetLoginRequestByInternalIdAsync(input.Id);
- var viewModel = BuildViewModelAsync(request, input.Id, input);
+ var request = await _interaction.GetLoginRequestByInternalIdAsync(input.Id!);
+ var viewModel = BuildViewModelAsync(request!, input.Id!, input);
- CompleteBackchannelLoginRequest result = null;
+ CompleteBackchannelLoginRequest? result = null;
// user clicked 'no' - send back the standard 'access_denied' response
if (input.Button == "no")
{
- result = new CompleteBackchannelLoginRequest(input.Id);
+ result = new CompleteBackchannelLoginRequest(input.Id!);
// emit event
- await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues))
+ await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request!.Client.ClientId, request.ValidatedResources.RawScopeValues))
.ConfigureAwait(false);
}
// user clicked 'yes' - validate the data
@@ -70,14 +70,14 @@ await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Cli
scopes = scopes.Where(x => x != StandardScopes.OfflineAccess);
}
- result = new CompleteBackchannelLoginRequest(input.Id)
+ result = new CompleteBackchannelLoginRequest(input.Id!)
{
ScopesValuesConsented = scopes.ToArray(),
Description = input.Description
};
// emit event
- await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, result.ScopesValuesConsented, false))
+ await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request!.Client.ClientId, request.ValidatedResources.RawScopeValues, result.ScopesValuesConsented, false))
.ConfigureAwait(false);
}
else
@@ -100,7 +100,7 @@ await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Cl
return View(viewModel);
}
- private ViewModel BuildViewModelAsync(BackchannelUserLoginRequest request, string id, InputModel model = null)
+ private ViewModel BuildViewModelAsync(BackchannelUserLoginRequest request, string id, InputModel? model = null)
{
if (request is null)
{
@@ -113,7 +113,7 @@ private ViewModel BuildViewModelAsync(BackchannelUserLoginRequest request, strin
throw new InvalidOperationException(_localizer["SubjectIds don't match."]);
}
- private ViewModel CreateConsentViewModel(InputModel model,
+ private ViewModel CreateConsentViewModel(InputModel? model,
string id,
BackchannelUserLoginRequest request)
{
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/InputModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/InputModel.cs
index 22acc02e5..d2b963e44 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/InputModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/InputModel.cs
@@ -4,10 +4,10 @@ namespace Aguacongas.TheIdServer.Duende.Quickstart.Ciba
{
public class InputModel
{
- public string Button { get; set; }
- public IEnumerable ScopesConsented { get; set; }
- public string Id { get; set; }
- public string Description { get; set; }
+ public string? Button { get; set; }
+ public IEnumerable? ScopesConsented { get; set; }
+ public string? Id { get; set; }
+ public string? Description { get; set; }
}
}
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ResourceViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ResourceViewModel.cs
index 75039a5df..236375ef0 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ResourceViewModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ResourceViewModel.cs
@@ -2,7 +2,7 @@
{
public class ResourceViewModel
{
- public string Name { get; set; }
- public string DisplayName { get; set; }
+ public string? Name { get; set; }
+ public string? DisplayName { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ScopeViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ScopeViewModel.cs
index 6022bf35c..765d63d48 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ScopeViewModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ScopeViewModel.cs
@@ -4,13 +4,13 @@ namespace Aguacongas.TheIdServer.Duende.Quickstart.Ciba
{
public class ScopeViewModel
{
- public string Name { get; set; }
- public string Value { get; set; }
- public string DisplayName { get; set; }
- public string Description { get; set; }
+ public string? Name { get; set; }
+ public string? Value { get; set; }
+ public string? DisplayName { get; set; }
+ public string? Description { get; set; }
public bool Emphasize { get; set; }
public bool Required { get; set; }
public bool Checked { get; set; }
- public IEnumerable Resources { get; set; }
+ public IEnumerable? Resources { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ViewModel.cs
index c2a26af8b..9debc9c2e 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ViewModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Ciba/ViewModel.cs
@@ -4,20 +4,20 @@ namespace Aguacongas.TheIdServer.Duende.Quickstart.Ciba
{
public class ViewModel
{
- public string Id { get; set; }
+ public string? Id { get; set; }
- public string ClientName { get; set; }
- public string ClientUrl { get; set; }
- public string ClientLogoUrl { get; set; }
+ public string? ClientName { get; set; }
+ public string? ClientUrl { get; set; }
+ public string? ClientLogoUrl { get; set; }
- public string BindingMessage { get; set; }
+ public string? BindingMessage { get; set; }
- public string Description { get; set; }
+ public string? Description { get; set; }
- public InputModel Input { get; set; }
+ public InputModel? Input { get; set; }
- public IEnumerable IdentityScopes { get; set; }
- public IEnumerable ApiScopes { get; set; }
+ public IEnumerable? IdentityScopes { get; set; }
+ public IEnumerable? ApiScopes { get; set; }
}
}
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentController.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentController.cs
index 47561b7b2..1e91ee709 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentController.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentController.cs
@@ -73,12 +73,12 @@ public async Task Index(ConsentInputModel model)
return this.LoadingPage("Redirect", result.RedirectUri);
}
- return Redirect(result.RedirectUri);
+ return Redirect(result.RedirectUri!);
}
if (result.HasValidationError)
{
- ModelState.AddModelError(string.Empty, result.ValidationError);
+ ModelState.AddModelError(string.Empty, result.ValidationError!);
}
if (result.ShowView)
@@ -100,10 +100,10 @@ private async Task ProcessConsent(ConsentInputModel model)
var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
if (request == null) return result;
- ConsentResponse grantedConsent = null;
+ ConsentResponse? grantedConsent = null;
// user clicked 'no' - send back the standard 'access_denied' response
- if (model?.Button == "no")
+ if (model.Button == "no")
{
grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied };
@@ -111,7 +111,7 @@ private async Task ProcessConsent(ConsentInputModel model)
await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues));
}
// user clicked 'yes' - validate the data
- else if (model?.Button == "yes")
+ else if (model.Button == "yes")
{
// if the user consented to some scope, build the response model
if (model.ScopesConsented != null && model.ScopesConsented.Any())
@@ -160,7 +160,7 @@ private async Task ProcessConsent(ConsentInputModel model)
return result;
}
- private async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null)
+ private async Task BuildViewModelAsync(string? returnUrl, ConsentInputModel? model = null)
{
var request = await _interaction.GetAuthorizationContextAsync(returnUrl);
if (request != null)
@@ -176,7 +176,7 @@ private async Task BuildViewModelAsync(string returnUrl, Conse
}
private ConsentViewModel CreateConsentViewModel(
- ConsentInputModel model, string returnUrl,
+ ConsentInputModel? model, string? returnUrl,
AuthorizationRequest request)
{
var client = request.Client;
@@ -193,11 +193,11 @@ private ConsentViewModel CreateConsentViewModel(
ClientLogoUrl = client.LogoUri,
AllowRememberConsent = client.AllowRememberConsent
};
- if (client.Properties.TryGetValue("PolicyUrl", out string policyUrl))
+ if (client.Properties.TryGetValue("PolicyUrl", out var policyUrl))
{
vm.PolicyUrl = policyUrl;
}
- if (client.Properties.TryGetValue("TosUrl", out string tosUrl))
+ if (client.Properties.TryGetValue("TosUrl", out var tosUrl))
{
vm.TosUrl = tosUrl;
}
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentInputModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentInputModel.cs
index 4b59bd7c7..a5335df0f 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentInputModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentInputModel.cs
@@ -6,10 +6,10 @@ namespace Aguacongas.TheIdServer.UI
{
public class ConsentInputModel
{
- public string Button { get; set; }
- public IEnumerable ScopesConsented { get; set; }
+ public string? Button { get; set; }
+ public IEnumerable? ScopesConsented { get; set; }
public bool RememberConsent { get; set; }
- public string ReturnUrl { get; set; }
- public string Description { get; set; }
+ public string? ReturnUrl { get; set; }
+ public string? Description { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentViewModel.cs
index 41c93cd37..2553187f6 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentViewModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ConsentViewModel.cs
@@ -6,17 +6,17 @@ namespace Aguacongas.TheIdServer.UI
{
public class ConsentViewModel : ConsentInputModel
{
- public string ClientName { get; set; }
- public string ClientUrl { get; set; }
- public string ClientLogoUrl { get; set; }
+ public string? ClientName { get; set; }
+ public string? ClientUrl { get; set; }
+ public string? ClientLogoUrl { get; set; }
- public string PolicyUrl { get; set; }
+ public string? PolicyUrl { get; set; }
- public string TosUrl { get; set; }
+ public string? TosUrl { get; set; }
public bool AllowRememberConsent { get; set; }
- public IEnumerable IdentityScopes { get; set; }
- public IEnumerable ApiScopes { get; set; }
+ public IEnumerable? IdentityScopes { get; set; }
+ public IEnumerable? ApiScopes { get; set; }
}
}
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ProcessConsentResult.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ProcessConsentResult.cs
index a838390a4..90a7a7258 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ProcessConsentResult.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ProcessConsentResult.cs
@@ -7,13 +7,13 @@ namespace Aguacongas.TheIdServer.UI
public class ProcessConsentResult
{
public bool IsRedirect => RedirectUri != null;
- public string RedirectUri { get; set; }
- public Client Client { get; set; }
+ public string? RedirectUri { get; set; }
+ public Client? Client { get; set; }
public bool ShowView => ViewModel != null;
- public ConsentViewModel ViewModel { get; set; }
+ public ConsentViewModel? ViewModel { get; set; }
public bool HasValidationError => ValidationError != null;
- public string ValidationError { get; set; }
+ public string? ValidationError { get; set; }
}
}
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ScopeViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ScopeViewModel.cs
index b0959b3e7..69fb93ae3 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ScopeViewModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Consent/ScopeViewModel.cs
@@ -4,9 +4,9 @@ namespace Aguacongas.TheIdServer.UI
{
public class ScopeViewModel
{
- public string Value { get; set; }
- public string DisplayName { get; set; }
- public string Description { get; set; }
+ public string? Value { get; set; }
+ public string? DisplayName { get; set; }
+ public string? Description { get; set; }
public bool Emphasize { get; set; }
public bool Required { get; set; }
public bool Checked { get; set; }
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationInputModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationInputModel.cs
index 91a56b546..46f766a57 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationInputModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationInputModel.cs
@@ -6,6 +6,6 @@ namespace Aguacongas.IdentityServer.UI.Device
{
public class DeviceAuthorizationInputModel : ConsentInputModel
{
- public string UserCode { get; set; }
+ public string? UserCode { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationViewModel.cs
index 91bb36c30..fa734e2ac 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationViewModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceAuthorizationViewModel.cs
@@ -6,7 +6,7 @@ namespace Aguacongas.IdentityServer.UI.Device
{
public class DeviceAuthorizationViewModel : ConsentViewModel
{
- public string UserCode { get; set; }
+ public string? UserCode { get; set; }
public bool ConfirmUserCode { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceController.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceController.cs
index e610082d2..b5f35630d 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceController.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Device/DeviceController.cs
@@ -1,9 +1,5 @@
// Project: Aguafrommars/TheIdServer
// Copyright (c) 2023 @Olivier Lefebvre
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
using Aguacongas.TheIdServer.UI;
using Duende.IdentityServer;
using Duende.IdentityServer.Events;
@@ -39,11 +35,14 @@ public DeviceController(
[HttpGet]
public async Task Index()
{
- string userCodeParamName = _options.Value.UserInteraction.DeviceVerificationUserCodeParameter;
- string userCode = Request.Query[userCodeParamName];
- if (string.IsNullOrWhiteSpace(userCode)) return View("UserCodeCapture");
+ var userCodeParamName = _options.Value.UserInteraction.DeviceVerificationUserCodeParameter;
+ var userCode = Request.Query[userCodeParamName];
+ if (string.IsNullOrWhiteSpace(userCode))
+ {
+ return View("UserCodeCapture");
+ }
- var vm = await BuildViewModelAsync(userCode);
+ var vm = await BuildViewModelAsync(userCode!);
if (vm == null) return View("Error");
vm.ConfirmUserCode = true;
@@ -67,10 +66,7 @@ public async Task UserCodeCapture(string userCode)
[ValidateAntiForgeryToken]
public Task Callback(DeviceAuthorizationInputModel model)
{
- if (model == null)
- {
- throw new ArgumentNullException(nameof(model));
- }
+ ArgumentNullException.ThrowIfNull(model);
return CallbackInternal(model);
}
@@ -90,10 +86,10 @@ private async Task ProcessConsent(DeviceAuthorizationInput
{
var result = new ProcessConsentResult();
- var request = await _interaction.GetAuthorizationContextAsync(model.UserCode);
+ var request = await _interaction.GetAuthorizationContextAsync(model.UserCode!);
if (request == null) return result;
- ConsentResponse grantedConsent = null;
+ ConsentResponse? grantedConsent = null;
// user clicked 'no' - send back the standard 'access_denied' response
if (model.Button == "no")
@@ -138,7 +134,7 @@ private async Task ProcessConsent(DeviceAuthorizationInput
if (grantedConsent != null)
{
// communicate outcome of consent back to identityserver
- await _interaction.HandleRequestAsync(model.UserCode, grantedConsent);
+ await _interaction.HandleRequestAsync(model.UserCode!, grantedConsent);
// indicate that's it ok to redirect back to authorization endpoint
result.RedirectUri = model.ReturnUrl;
@@ -147,13 +143,13 @@ private async Task ProcessConsent(DeviceAuthorizationInput
else
{
// we need to redisplay the consent UI
- result.ViewModel = await BuildViewModelAsync(model.UserCode, model);
+ result.ViewModel = await BuildViewModelAsync(model.UserCode!, model);
}
return result;
}
- private async Task BuildViewModelAsync(string userCode, DeviceAuthorizationInputModel model = null)
+ private async Task BuildViewModelAsync(string userCode, DeviceAuthorizationInputModel? model = null)
{
var request = await _interaction.GetAuthorizationContextAsync(userCode);
if (request != null)
@@ -164,7 +160,7 @@ private async Task BuildViewModelAsync(string user
return null;
}
- private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, DeviceAuthorizationInputModel model, DeviceFlowAuthorizationRequest request)
+ private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, DeviceAuthorizationInputModel? model, DeviceFlowAuthorizationRequest request)
{
var client = request.Client;
var vm = new DeviceAuthorizationViewModel
@@ -180,11 +176,11 @@ private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, Dev
ClientLogoUrl = client.LogoUri,
AllowRememberConsent = client.AllowRememberConsent
};
- if (client.Properties.TryGetValue("PolicyUrl", out string policyUrl))
+ if (client.Properties.TryGetValue("PolicyUrl", out var policyUrl))
{
vm.PolicyUrl = policyUrl;
}
- if (client.Properties.TryGetValue("TosUrl", out string tosUrl))
+ if (client.Properties.TryGetValue("TosUrl", out var tosUrl))
{
vm.TosUrl = tosUrl;
}
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsController.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsController.cs
index 430317da3..fa6881015 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsController.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsController.cs
@@ -14,8 +14,8 @@ public class DiagnosticsController : Controller
{
public async Task Index()
{
- var localAddresses = new string[] { "127.0.0.1", "::1", HttpContext.Connection.LocalIpAddress.ToString() };
- if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress.ToString()))
+ var localAddresses = new string[] { "127.0.0.1", "::1", HttpContext.Connection.LocalIpAddress!.ToString() };
+ if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress!.ToString()))
{
return NotFound();
}
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsViewModel.cs
index 94452eb61..f5fbbc750 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsViewModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Diagnostics/DiagnosticsViewModel.cs
@@ -14,18 +14,18 @@ public DiagnosticsViewModel(AuthenticateResult result)
{
AuthenticateResult = result;
- if (!result.Properties.Items.TryGetValue("client_list", out string encoded))
+ if (result.Properties?.Items is null || !result.Properties.Items.TryGetValue("client_list", out var encoded))
{
return;
}
- var bytes = Base64Url.Decode(encoded);
+ var bytes = Base64Url.Decode(encoded!);
var value = Encoding.UTF8.GetString(bytes);
Clients = JsonConvert.DeserializeObject(value);
}
- public AuthenticateResult AuthenticateResult { get; }
- public IEnumerable Clients { get; } = new List();
+ public AuthenticateResult? AuthenticateResult { get; }
+ public IEnumerable? Clients { get; } = new List();
}
}
\ No newline at end of file
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Extensions.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Extensions.cs
index b542d2b0e..5d0d2f491 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Extensions.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Extensions.cs
@@ -2,7 +2,6 @@
// Copyright (c) 2023 @Olivier Lefebvre
using Duende.IdentityServer.Models;
using Microsoft.AspNetCore.Mvc;
-using System;
namespace Aguacongas.TheIdServer.UI
{
@@ -18,10 +17,10 @@ public static bool IsNativeClient(this AuthorizationRequest context)
&& !context.RedirectUri.StartsWith("http", StringComparison.Ordinal);
}
- public static IActionResult LoadingPage(this Controller controller, string viewName, string redirectUri)
+ public static IActionResult LoadingPage(this Controller controller, string? viewName, string? redirectUri)
{
controller.HttpContext.Response.StatusCode = 200;
- controller.HttpContext.Response.Headers["Location"] = "";
+ controller.HttpContext.Response.Headers.Location = "";
return controller.View(viewName, new RedirectViewModel { RedirectUrl = redirectUri });
}
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/Home/ErrorViewModel.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/Home/ErrorViewModel.cs
index 413ac6876..aa50a0466 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/Home/ErrorViewModel.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/Home/ErrorViewModel.cs
@@ -15,6 +15,6 @@ public ErrorViewModel(string error)
Error = new ErrorMessage { Error = error };
}
- public ErrorMessage Error { get; set; }
+ public ErrorMessage? Error { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Aguacongas.TheIdServer.Duende/Quickstart/SecurityHeadersAttribute.cs b/src/Aguacongas.TheIdServer.Duende/Quickstart/SecurityHeadersAttribute.cs
index 6421ff544..ce2f00182 100644
--- a/src/Aguacongas.TheIdServer.Duende/Quickstart/SecurityHeadersAttribute.cs
+++ b/src/Aguacongas.TheIdServer.Duende/Quickstart/SecurityHeadersAttribute.cs
@@ -17,13 +17,13 @@ public override void OnResultExecuting(ResultExecutingContext context)
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options"))
{
- context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff");
+ context.HttpContext.Response.Headers.XContentTypeOptions = "nosniff";
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options"))
{
- context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
+ context.HttpContext.Response.Headers.XFrameOptions = "SAMEORIGIN";
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
@@ -53,19 +53,19 @@ public override void OnResultExecuting(ResultExecutingContext context)
// once for standards compliant browsers
if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy"))
{
- context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp);
+ context.HttpContext.Response.Headers.ContentSecurityPolicy = csp;
}
// and once again for IE
if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy"))
{
- context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp);
+ context.HttpContext.Response.Headers["X-Content-Security-Policy"] = csp;
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
var referrer_policy = "no-referrer";
if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy"))
{
- context.HttpContext.Response.Headers.Add("Referrer-Policy", referrer_policy);
+ context.HttpContext.Response.Headers["Referrer-Policy"] = referrer_policy;
}
}
}
diff --git a/src/Aguacongas.TheIdServer.Duende/README.md b/src/Aguacongas.TheIdServer.Duende/README.md
index 011dcf945..bc44ee382 100644
--- a/src/Aguacongas.TheIdServer.Duende/README.md
+++ b/src/Aguacongas.TheIdServer.Duende/README.md
@@ -2,1007 +2,4 @@
> TheIdServer use [Duende IdentityServer](https://duendesoftware.com/products/identityserver), for a commercial use you need to [acquire a license](https://duendesoftware.com/products/identityserver#pricing).
-The server obtains configuration from *appsettings.json*, *appsettings.{Environment}.json*, command-line arguments, or environment variables.
-
-Read [Configuration in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) for more information.
-
-## Installation
-
-### From Terraform
-
-The [Terraform](https://terraform.io) [Helm](https://helm.sh) module [theidserver](https://registry.terraform.io/modules/Aguafrommars/theidserver/helm/latest) make the deployement of TheIdServer easy.
-To deploy the Duende version choose the [aguacongas/theidserver.duende image](https://hub.docker.com/r/aguacongas/theidserver.duende).
-
-``` hcl
-provider "helm" {
- kubernetes {
- config_path = var.kubeconfig_path
- }
-}
-
-module "theidserver" {
- source = "Aguafrommars/theidserver/helm"
-
- host = "theidserver.com"
- tls_issuer_name = "letsencrypt"
- tls_issuer_kind = "ClusterIssuer"
-
- image = {
- repository = "aguacongas/theidserver.duende"
- pullPolicy = "Always"
- tag = "next"
- }
-}
-```
-
-### From Helm
-
-The [theidserver](https://hub.helm.sh/packages/helm/aguafrommars/theidserver) [Helm](https://helm.sh) chart is available in [hub.helm.sh](https://hub.helm.sh).
-
-#### Install
-
-``` bash
-helm repo add aguafrommars https://aguafrommars.github.io/helm
-helm install aguafrommars theidserver --set theidserver.mysql.db.password=my-P@ssword --set image.repository=aguacongas/theidserver.duende
-```
-
-> By default the helm char install the IS4 version, to install the Duende version your need to set `image.repository=aguacongas/theidserver.duende`.
-
-#### Upgrade
-
-Follow upgrades intstructions in the [chart readme](https://github.com/Aguafrommars/helm/blob/main/charts/theidserver/README.md#upgrade).
-
-### From Docker
-
-A [server's Linux image](https://hub.docker.com/r/aguacongas/theidserver.duende) is available on Docker Hub.
-
-[*sample/MultiTiers/Aguacongas.TheIdServer.Private/Dockerfile.Duende-private*](../../sample/MultiTiers/Aguacongas.TheIdServer.Private/Dockerfile.Duende-private) demonstrates how to create an image from the [server image](https://hub.docker.com/r/aguacongas/theidserver) to run a private Linux server container.
-
-[*sample/MultiTiers/Aguacongas.TheIdServer.Public/Dockerfile.Duende-public*](../../sample/MultiTiers/Aguacongas.TheIdServer.Public/Dockerfile.Duende-public) illustrates how to create an image from the [server image](https://hub.docker.com/r/aguacongas/theidserver) to run a public Linux server container.
-
-Read [Hosting ASP.NET Core images with Docker over HTTPS](https://docs.microsoft.com/en-us/aspnet/core/security/docker-https) to set up the HTTPS certificate.
-
-#### Kubernetes sample
-
-[/sample/Kubernetes/README.md](/sample/Kubernetes/README.md) contains a sample to set up a solution with Kubernetes.
-
-> The sample use the IS4 version but you just need to use `aguacongas/theidserver.duende` as docker image in the deployement file.
-
-### From dotnet new template
-
-The template [TheIdServer.Duende.Template](https://github.com/Aguafrommars/Templates) can be use to setup a TheIdServer solution.
-
-#### Install
-
-```bash
-dotnet new -i TheIdServer.Duende.Template
-```
-
-#### Use
-
-```bash
-> dotnet new tisduende -o TheIdServer
-The template "TheIdServer.Duende" was created successfully.
-
-Processing post-creation actions...
-Running 'dotnet restore' on TheIdServer\TheIdServer.sln...
- Determining projects to restore...
- Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\WebAssembly.Net.Http\WebAssembly.Net.Http.csproj (in 114 ms).
- Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\src\TheIdServer.BlazorApp\TheIdServer.BlazorApp.csproj (in 916 ms).
- Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\Microsoft.AspNetCore.Components.Testing\Microsoft.AspNetCore.Components.Testing.csproj (in 1.08 sec).
- Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\src\TheIdServer\TheIdServer.csproj (in 2.03 sec).
- Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\TheIdServer.Test\TheIdServer.Test.csproj (in 2.04 sec).
- Restored C:\Projects\Perso\Templates\artifacts\TheIdServer\test\TheIdServer.IntegrationTest\TheIdServer.IntegrationTest.csproj (in 2.04 sec).
-Restore succeeded.
-```
-
-### From NuGet Packages
-
-If you need more customization, you can use published NuGet packages.
-[sample/MultiTiers](sample/MultiTiers) contains a sample to build server and API from NuGet packages.
-
-> The sample use IS4 version but you just need to remplace IS4 by Duende in package reference to use the Duende version.
-
-### From Github Release
-
-Choose your release in the [list of releases](https://github.com/Aguafrommars/TheIdServer/releases) and download the server zip.
-Unzip in the destination of your choice. Unzip in the destination of your choice. As with any ASP.NET Core web site, it can run in IIS or as a stand-alone server using your chosen platform.
-
-Read [Host and deploy ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/) for more information.
-
-## Configure data protection
-
-[Data protection](../../doc/DATA_PROTECTION.md) provides details on data protection configuration.
-
-## Configure site
-
-The site name is defined by *SiteOptions:TheIdServer*.
-
-```json
-"SiteOptions": {
- "Name": "TheIdServer"
-}
-```
-
-The site stylecheets are *wwwroot/lib/bootstrap/css/bootstrap.css* and *wwwroot/css/site.min.css*.
-The site logo is *wwwroot/logo.png*.
-And the favicon is *wwwroot/favicon.ico*.
-
-By replacing those files you can redefined the site style by yours.
-
-### Configure account options
-
-The section *AccountOptions* is bound to [`AccountOptions`](../Aguacongas.TheIdServer.Shared/Quickstart/Account/AccountOptions.cs).
-
-```json
-"AccountOptions": {
- "AllowLocalLogin": true,
- "AllowRememberLogin": true,
- "RememberMeLoginDuration": "30.00:00:00",
- "ShowLogoutPrompt": true,
- "AutomaticRedirectAfterSignOut": false,
- "InvalidCredentialsErrorMessage": "Invalid username or password",
- "ShowForgotPassworLink": true,
- "ShowRegisterLink": true,
- "ShowResendEmailConfirmationLink": true
-}
-```
-
-## Configure ASP.Net Core Identity options
-
-The section **IdentityOptions** is binded to the class [`Microsoft.AspNetCore.Identity.IdentityOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.identityoptions).
-So you can set any ASP.Net Core Identity options you want from configuration
-
-```json
-"IdentityOptions": {
- "User": {
- "AllowedUserNameCharacters": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+ "
- },
- "SignIn": {
- "RequireConfirmedAccount": true
- }
-}
-```
-
-## Configure Duende IdentityServer
-
-The section **IdentityServerOptions** is binded to the class [`Duende.IdentityServer.Configuration.IdentityServerOptions`](https://docs.duendesoftware.com/identityserver/v5/reference/options/).
-So you can set any Duende IdentityServer options you want from configuration (but key management options).
-
-```json
-"IdentityServerOptions": {
- "Events": {
- "RaiseErrorEvents": true,
- "RaiseInformationEvents": true,
- "RaiseFailureEvents": true,
- "RaiseSuccessEvents": true
- },
- "Endpoints": {
- "EnableJwtRequestUri": true
- }
-}
-```
-
-### Discovery document customs entries
-
-You can add customs entries to the genererated discovery document with *IdentityServerOptions* sub section *CustomEntriesOfStringArray*, *CustomEntriesOfString* and *CustomEntriesOfBool*
-
-```json
-"IdentityServerOptions": {
- "CustomEntriesOfStringArray": {
- "token_endpoint_auth_signing_alg_values_supported": [
- "RS256",
- "ES256",
- "ES384",
- "ES512",
- "PS256",
- "PS384",
- "PS512",
- "RS384",
- "RS512"
- ]
- }
-}
-```
-
-The sample above will add `"token_endpoint_auth_signing_alg_values_supported"` node to the generated document.
-
-### Mutual TLS client certificate options
-
-When Muutal TLS is enabled, you can configure the client certificate authentication options with `CertificateAuthenticationOptions` section.
-
-```json
-"IdentityServerOptions": {
- "MutualTls": {
- "Enabled": true
- }
-},
-"CertificateAuthenticationOptions": {
- "AllowedCertificateTypes": "All",
- "ValidateCertificateUse": false,
- "ValidateValidityPeriod": false,
- "RevocationMode": "NoCheck"
-}
-```
-
-### Retrieves client certificates fron HTTP request header
-
-When Mutual TLS is enabled the client certificate can be read in PEM format from request header. For exemple if you use a kubernetes NGINX ingress you can configure it to send the client certificate to the backend in the *ssl-client-cert* header.
-See [Client Certificate Authentication](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#client-certificate-authentication).
-
-To retrieve the client certificate from the request header confiure the `MutualTls` sub section like :
-
-```json
-"IdentityServerOptions": {
- "MutualTls": {
- "Enabled": true,
- "PEMHeader": "ssl-client-cert"
- }
-}
-```
-
-### Configure Server-side sessions
-
-Read [Server-side sessions](../../doc/SERVER_SIDE_SESSIONS.md)
-
-## Configure stores
-
-### Using Entity Framework Core
-
-The server supports *SqlServer*, *Sqlite*, *MySql*, *PostgreSQL*, *Oracle*, and *InMemory* databases.
-Use **DbType** to the define the database engine.
-
-```json
-"DbType": "SqlServer"
-```
-
-And **ConnectionStrings:DefaultConnection** to define the connection string.
-
-```json
-"ConnectionStrings": {
- "DefaultConnection": "Data Source=(LocalDb)\\MSSQLLocalDB;database=TheIdServer;trusted_connection=yes;"
-}
-```
-
-> A [devart dotConnect for Oracle](https://www.devart.com/dotconnect/oracle/) license is a requirement for Oracle.
-
-### Using RavenDb
-
-Use **DbType** to the define the RavenDb database engine.
-
-```json
-"DbType": "RavenDb"
-```
-
-And **RavenDbOptions** to define the RavenDb options.
-
-```json
-"RavenDbOptions": {
- "Urls": [
- "https://a.ravendb.local",
- "https://b.ravendb.local",
- "https://c.ravendb.local"
- ],
- "Database": "TheIdServer",
- "CertificatePath": "cluster.admin.client.certificate.pfx",
- "CertificatePassword": "p@$$w0rd"
-}
-```
-
-> As no `DbContext` will be registered, you cannot store signing keys in EF but you can choose the `RavenDb` storage kind (see [Configure signin keys](#configure-signing-key)):
-```json
-"IdentityServer": {
- "Key": {
- "StorageKind": "RavenDb"
- }
-},
-"DataProtectionOptions": {
- "StorageKind": "RavenDb"
-}
-```
-> The server support RavenDb 4.1 and above.
-
-### Using MongoDb
-
-Use **DbType** to the define the RavenDb database engine.
-
-```json
-"DbType": "MongoDb"
-```
-
-And **ConnectionStrings:DefaultConnection** to define the connection string.
-
-```json
-"ConnectionStrings": {
- "DefaultConnection": "mongodb+srv://theidserver:theidserverpwd@cluster0.fvkfz.mongodb.net/TheIdServer?retryWrites=true&w=majority"
-}
-```
-
-> We cannot used another database than the default database defined in the connection string.
-
-> As no `DbContext` will be registered, you cannot store signing keys in EF but you can choose the `MongoDb` storage kind (see [Configure signin keys](#configure-signing-key)):
-```json
-"IdentityServer": {
- "Key": {
- "StorageKind": "MongoDb"
- }
-},
-"DataProtectionOptions": {
- "StorageKind": "MongoDb"
-}
-```
-
-### Using the API
-
-![public-private.svg](../../doc/assets/public-pribate.png)
-
-If you don't want to expose a database with your server, you can set up a second server on a private network accessing the database and use this private server API to access data.
-
-```json
-"Proxy": true,
-"PrivateServerAuthentication": {
- "Authority": "https://theidserverprivate",
- "ApiUrl": "https://theidserverprivate/api",
- "ClientId": "public-server",
- "ClientSecret": "84137599-13d6-469c-9376-9e372dd2c1bd",
- "Scope": "theidserveradminapi",
- "HttpClientName": "is4"
-},
-"SignalR": {
- "HubUrl": "https://theidserverprivate/providerhub"
- "HubOptions": {
- "EnableDetailedErrors": true
- },
- "UseMessagePack": true
-}
-```
-
-#### Proxy
-
-Start the server with proxy mode enabled.
-
-#### PrivateServerAuthentication
-
-Defines how to authenticate the public server on private server API.
-
-#### SignalR
-
-Defines the [SignalR client](https://docs.microsoft.com/en-us/aspnet/core/signalr/dotnet-client&tabs=visual-studio) configuration.
-This client is used to update the external provider configuration of a running instance. When an external provider configuration changes, the API sends a SignalR notification to inform other running instances.
-
-For more information, read [Load balancing scenario](https://github.com/Aguafrommars/DymamicAuthProviders/wiki/Load-balancing-scenario).
-
-The SignalR hub accepts requests at */providerhub* and supports the [MessagePack](https://msgpack.org/index.html) protocol.
-
-For more information, read [Use MessagePack Hub Protocol in SignalR for ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/signalr/messagepackhubprotocol).
-
-### Database migration and data seeding
-
-Starting the server with the **/seed** command-line argument creates the database with initial data. Alternatively, configure the server with the following to create a database with initial users, protected resources, identity resources, and clients.
-
-```json
-"Migrate": true,
-"Seed": true
-```
-
-#### Roles
-
-* **Is4-Writer** authorizes users in this role to write data.
-* **Is4-Reader** permits users in this role to read data.
-
-#### Identity resources
-
-* **profile** default profile resource with **role** claim
-* **openid** default OpenID resource
-* **address** default address resource
-* **email** default email resource
-* **phone** default phone resource
-
-#### Users
-
-Users defined in `InitialData:Users` configuration section are loaded and stored to the DB.
-
-Default configuration:
-
-```json
-"InitialData": {
-...
- "Users": [
- {
- "UserName": "alice",
- "Email": "alice@theidserver.com",
- "EmailConfirmed": true,
- "PhoneNumber": "+41766403736",
- "PhoneNumberConfirmed": true,
- "Password": "Pass123$",
- "Roles": [
- "Is4-Writer",
- "Is4-Reader"
- ],
- "Claims": [
- {
- "ClaimType": "name",
- "ClaimValue": "Alice Smith"
- },
- {
- "ClaimType": "given_name",
- "ClaimValue": "Alice"
- },
- {
- "ClaimType": "family_name",
- "ClaimValue": "Smith"
- },
- {
- "ClaimType": "middle_name",
- "ClaimValue": "Alice Smith"
- },
- {
- "ClaimType": "nickname",
- "ClaimValue": "alice"
- },
- {
- "ClaimType": "website",
- "ClaimValue": "http://alice.com"
- },
- {
- "ClaimType": "address",
- "ClaimValue": "{ \"street_address\": \"One Hacker Way\", \"locality\": \"Heidelberg\", \"postal_code\": \"69118\", \"country\": \"Germany\" }",
- },
- {
- "ClaimType": "birthdate",
- "ClaimValue": "1970-01-01"
- },
- {
- "ClaimType": "zoneinfo",
- "ClaimValue": "ch"
- },
- {
- "ClaimType": "gender",
- "ClaimValue": "female"
- },
- {
- "ClaimType": "profile",
- "ClaimValue": "http://alice.com/profile"
- },
- {
- "ClaimType": "locale",
- "ClaimValue": "fr"
- },
- {
- "ClaimType": "picture",
- "ClaimValue": "http://alice.com/picture"
- }
- ]
- }
- ]
-}
-```
-
-> A user with *Is4-Writer* and *Is4-Reader* roles is required to use the admin app.
-
-#### Protected resources (API)
-
-Apis defined in `InitialData:Apis` configuration section are loaded and stored to the DB.
-
-Default configuration:
-
-```json
-"InitialData": {
-...
- "Apis": [
- {
- "Name": "theidserveradminapi",
- "DisplayName": "TheIdServer admin API",
- "UserClaims": [
- "name",
- "role"
- ],
- "ApiSecrets": [
- {
- "Type": "SharedSecret",
- "Value": "5b556f7c-b3bc-4b5b-85ab-45eed0cb962d"
- }
- ],
- "Scopes": [
- "theidserveradminapi"
- ]
- }
- ],
-}
-```
-
-> The api **theidserveradminapi** is required for the admin app.
-
-#### ApiScopes
-
-ApiScopes defined in `InitialData:ApiScopes` configuration section are loaded and stored to the DB.
-
-Default configuration:
-
-```json
-"InitialData": {
-...
- "ApiScopes": [
- {
- "Name": "theidserveradminapi",
- "DisplayName": "TheIdServer admin API",
- "UserClaims": [
- "name",
- "role"
- ]
- }
- ],
-}
-```
-
-> The scope **theidserveradminapi** is required for the admin app.
-
-#### Clients
-
-Clients defined in `InitialData:Clients` configuration section are loaded and stored to the DB.
-
-Default configuration:
-
-```json
-"InitialData": {
-...
- "Clients": [
- {
- "ClientId": "theidserveradmin",
- "ClientName": "TheIdServer admin SPA Client",
- "ClientUri": "https://localhost:5443/",
- "ClientClaimsPrefix": null,
- "AllowedGrantTypes": [ "authorization_code" ],
- "RequirePkce": true,
- "RequireClientSecret": false,
- "BackChannelLogoutSessionRequired": false,
- "FrontChannelLogoutSessionRequired": false,
- "RedirectUris": [
- "http://localhost:5001/authentication/login-callback",
- "https://localhost:5443/authentication/login-callback"
- ],
- "PostLogoutRedirectUris": [
- "http://localhost:5001/authentication/logout-callback",
- "https://localhost:5443/authentication/logout-callback"
- ],
- "AllowedCorsOrigins": [
- "http://localhost:5001",
- "https://localhost:5443"
- ],
- "AllowedScopes": [
- "openid",
- "profile",
- "theidserveradminapi"
- ],
- "AccessTokenType": "Reference"
- },
- {
- "ClientId": "public-server",
- "ClientName": "Public server Credentials Client",
- "ClientClaimsPrefix": null,
- "AllowedGrantTypes": [ "client_credentials" ],
- "ClientSecrets": [
- {
- "Type": "SharedSecret",
- "Value": "84137599-13d6-469c-9376-9e372dd2c1bd"
- }
- ],
- "Claims": [
- {
- "Type": "role",
- "Value": "Is4-Writer"
- },
- {
- "Type": "role",
- "Value": "Is4-Reader"
- }
- ],
- "BackChannelLogoutSessionRequired": false,
- "FrontChannelLogoutSessionRequired": false,
- "AllowedScopes": [
- "openid",
- "profile",
- "theidserveradminapi"
- ],
- "AccessTokenType": "Reference"
- }
- ],
-}
-```
-
-> The client **theidserveradmin** is required by the admin app.
-> The client **public-server** is required to call web apis and server side prerendering of the admin app.
-
-## Configure Signing Key
-
-### Keys rotatation (recommanded)
-
-TheIdServer can be configured with a keys rotation mechanism instead of a single key.
-Read [Keys rotation](../../doc/KEYS_ROTATION.md) to know how to configure it.
-
-```json
-"IdentityServer": {
- "Key": {
- "Type": "KeysRotation",
- "StorageKind": "EntityFramework"
- }
-}
-```
-
-### From file
-
-```json
-"IdentityServer": {
- "Key": {
- "Type": "File",
- "FilePath": "{path to the .pfx}",
- "Password": "{.pfx password}"
- }
-}
-```
-
-### From store
-
-Read [Example: Deploy to Azure Websites](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization#example-deploy-to-azure-websites)
-
-## Configure the Email service
-
-By default, the server uses [SendGrid](https://sendgrid.com/) to send Emails by calling the API at */api/email*
-
-```json
-"SendGridUser": "your user",
-"SendGridKey": "your SendGrid key"
-```
-
-### Use your API
-
-If you prefer to use your Email sender, implement a Web API receiving a POST request with the json:
-
-```json
-{
- "subject": "Email subject",
- "message": "Email message",
- "addresses": [
- "an-address@aguacongas.con"
- ]
-}
-```
-
-And update the *EmailApiAuthentication* configuration section:
-
-```json
-"EmailApiAuthentication": {
- "Authority": "https://localhost:5443",
- "ApiUrl": "https://localhost:5443/api/email",
- "ClientId": "public-server",
- "ClientSecret": "84137599-13d6-469c-9376-9e372dd2c1bd",
- "Scope": "theidserveradminapi",
- "HttpClientName": "email"
-}
-```
-
-> If you want to use the same authentication configuration and token for both *EmailApi* and *PrivateServer*, you can simplify it by sharing the same **HttpClientName**.
-
-```json
-"EmailApiAuthentication": {
- "ApiUrl": "https://localhost:5443/api/email",
- "HttpClientName": "is4"
-}
-```
-
-## Configure the 2fa authenticator issuer
-
-By default, the issuer for the 2fa authenticator is **Aguacongas.TheIdServer**.
-To update this value, set **AuthenticatorIssuer** with your issuer.
-
-```json
-"AuthenticatorIssuer": "TheIdServer"
-```
-
-## Configure the API
-
-### Authentication
-
-The *ApiAuthentication* section defines the authentication configuration for the API.
-
-```json
-"ApiAuthentication": {
- "Authority": "https://localhost",
- "RequireHttpsMetadata": false,
- "SupportedTokens": "Both",
- "ApiName": "theidserveradminapi",
- "ApiSecret": "5b556f7c-b3bc-4b5b-85ab-45eed0cb962d",
- "EnableCaching": true,
- "CacheDuration": "0:10:0",
- "LegacyAudienceValidation": true
-}
-```
-
-### Documentation endpoint
-
-To enable the API documentation, set **EnableOpenApiDoc** to `true`.
-
-```json
-"EnableOpenApiDoc": true
-```
-
-Use the section *SwaggerUiSettings* to configure the swagger client authentication.
-
-```json
-"SwaggerUiSettings": {
- "OAuth2Client": {
- "ClientId": "theidserver-swagger",
- "AppName": "TheIdServer Swagger UI",
- "UsePkceWithAuthorizationCodeGrant": true
- },
- "WithCredentials": true
-}
-```
-
-### CORS
-
-The section *CorsAllowedOrigin* defines allowed CORS origins.
-
-```json
-"CorsAllowedOrigin": [
- "http://localhost:5001"
-]
-```
-
-## Configure HTTPS
-
-To disable HTTPS, set **DisableHttps** to `false`.
-
-```json
-"DisableHttps": true
-```
-
-If you use a self-signed certificate, you can disable strict-SSL by settings **DisableStrictSsl** to `true`.
-
-```json
-"DisableStrictSsl": true
-```
-
-### Configure Forwarded Headers
-
-The section **ForwardedHeadersOptions** is bound to the class [`Microsoft.AspNetCore.Builder.ForwardedHeadersOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.forwardedheadersoptions).
-
-```json
-"ForwardedHeadersOptions": {
- "ForwardedHeaders": "All"
-}
-```
-
-### Force HTTPS scheme
-
-Some reverses proxies don't' forward headers. You can force HTTP requests schemes to https by settings ForceHttpsScheme.
-
-```json
-"ForceHttpsScheme": true
-```
-
-## Configure the provider hub
-
-The [Aguacongas.AspNetCore.Authentication library](https://github.com/Aguafrommars/DymamicAuthProviders) dynamically configures external providers.
-In a [load-balanced](https://github.com/Aguafrommars/DymamicAuthProviders/wiki/Load-balancing-scenario) configuration, the provider hub informs other running instances that an external provider configuration changes.
-The **SignalR** section defines the configuration for both the SignalR hub and the client.
-
-```json
-"SignalR": {
- "HubUrl": "https://theidserverprivate/providerhub",
- "HubOptions": {
- "EnableDetailedErrors": true
- },
- "UseMessagePack": true,
- "RedisConnectionString": "redis:6379",
- "RedisOptions": {
- "Configuration": {
- "ChannelPrefix": "TheIdServer"
- }
- }
-}
-```
-
-If needed, the hub can use a [Redis backplane](https://docs.microsoft.com/en-us/aspnet/core/signalr/redis-backplane). **SignalR:RedisConnectionString** and **SignalR:RedisOptions** configures the backplane.
-**SignalR:RedisOptions** is bound to an instance of [`Microsoft.AspNetCore.SignalR.StackExchangeRedis.RedisOptions`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.signalr.stackexchangeredis.redisoptions?view=aspnetcore-3.0) at startup.
-
-## Configure logs
-
-The **Serilog** section defines the [Serilog](https://serilog.net/) configuration.
-
-```json
-"Serilog": {
- "LevelSwitches": {
- "$controlSwitch": "Information"
- },
- "MinimumLevel": {
- "ControlledBy": "$controlSwitch"
- },
- "WriteTo": [
- {
- "Name": "Seq",
- "Args": {
- "serverUrl": "http://localhost:5341",
- "controlLevelSwitch": "$controlSwitch",
- "apiKey": "DVYuookX2vOq078fuOyJ"
- }
- },
- {
- "Name": "Console",
- "Args": {
- "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}",
- "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Literate, Serilog.Sinks.Console"
- }
- },
- {
- "Name": "Debug",
- "Args": {
- "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}"
- }
- }
- ],
- "Enrich": [
- "FromLogContext",
- "WithMachineName",
- "WithThreadId"
- ]
-}
-```
-For more details, read [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration/blob/dev/README.md).
-
-## Configure claims providers
-
-[Claims provider](../../doc/CLAIMS_PROVIDER.md) provides details on claims proivder configuration.
-
-## Configure token cleaner
-
-The token cleaner task removes expired tokens periodically. To configure the interval, use **TokenCleanupInterval**.
-
-```json
-"TokenCleanupInterval": "00:05:00"
-```
-
-To disable the task, use **DisableTokenCleanup**.
-
-```json
-"DisableTokenCleanup": true
-```
-
-> The task is not enabled on proxy server.
-
-## Configure Dynamic client registration allowed contacts a host
-
-The server supports [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html).
-
-New client registration is allowed to users with the **Is4-Writer** role by sending the user access token or to contacts defined in *DynamicClientRegistrationOptions* section.
-
-```json
-"DynamicClientRegistrationOptions": {
- "AllowedContacts": [
- {
- "Contact": "certification@oidf.org",
- "AllowedHosts": [
- "www.certification.openid.net"
- ]
- }
- ]
-}
-```
-
-It this case, the client registration request must contain the *contacts* array.
-
-**request sample**
-
-```json
-{
- "client_name": "oidc_cert_client gUPPBlHIEAqNOYR",
- "grant_types": [
- "authorization_code"
- ],
- "response_types": [
- "code"
- ],
- "redirect_uris": [
- "https://www.certification.openid.net/test/a/theidserver/callback"
- ],
- "contacts": [
- "certification@oidf.org"
- ]
-}
-```
-
-## Configure Jwt request validator
-
-Tokens returned by request_uri parameter are validated using the rules defined in *TokenValidationParameters* section. By default, the following rules are defined.
-
-```json
-"TokenValidationParameters": {
- "ValidateIssuer": false,
- "ValidateAudience": false,
- "ValidateIssuerSigningKey": false,
- "ValidateLifetime": false,
- "RequireAudience": false,
- "RequireExpirationTime": false,
- "RequireSignedTokens": false
-}
-```
-
-> To enable JWT request uri, set *EnableJwtRequestUri* to true in *IdentityServerOptions:Endpoints*
-> ```json
-> "IdentityServerOptions": {
-> "Endpoints": {
-> "EnableJwtRequestUri": true
-> }
-> },
-> ```
-
-## Configure WS-Federation endpoint
-
-Read [Aguacongas.IdentityServer.WsFederation.Duende](../IdentityServer/Duende/Aguacongas.IdentityServer.WsFederation.Duende/README.md)
-
-## Configure CIBA notification service
-
-Read [DUENDE CIBA INTEGRATION/Notification service](../../doc/CIBA.md#Notification-service)
-
-## Use the client to override the default configuration
-
-The server and the blazor app integrate [Aguafrommars/DynamicConfiguration](https://github.com/Aguafrommars/DynamicConfiguration). Most of the configuration can be ovveriden using the blazor app.
-
-Use **DynamicConfigurationOptions** to define the dynamic configuration provider.
-
-```json
-"DynamicConfigurationOptions": {
- "ProviderType": "Aguacongas.DynamicConfiguration.Redis.RedisConfigurationProvider, Aguacongas.DynamicConfiguration.Redis"
-}
-```
-Use **RedisConfigurationOptions** section to configure the Redis db.
-
-```json
-"RedisConfigurationOptions": {
- "ConnectionString": "localhost",
- "HashKey": "Aguacongas.TheIdServer.Duende",
- "Channel": "Aguacongas.TheIdServer.Duende.Channel"
-}
-```
-
-## Health checks
-
-The server expose an health checks enpoint you can use for docker on kubernetes at **/healthz**.
-
-The endpoit return a json reponse depending on the store kind used and redis dependencies :
-
-```json
-{
- "status": "Healthy",
- "results": {
- "ConfigurationDbContext": {
- "status": "Healthy"
- },
- "OperationalDbContext": {
- "status": "Healthy"
- },
- "ApplicationDbContext": {
- "status": "Healthy"
- },
- "DynamicConfigurationRedis": {
- "status": "Healthy"
- }
- }
-}
-```
-
-## Configure OpenTelemetry
-
-[Configure OpenTelemetry doc](../../doc/OPEN_TELEMETRY.md) provides details on [OpenTelemetry](https://opentelemetry.io/) configuration.
-
-## Additional resources
-
-* [Host and deploy ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/)
-* [DymamicAuthProviders](https://github.com/Aguafrommars/DymamicAuthProviders)
-* [Set up a Redis backplane for ASP.NET Core SignalR scale-out](https://docs.microsoft.com/en-us/aspnet/core/signalr/redis-backplane)
-* [Microsoft.AspNetCore.SignalR.StackExchangeRedis.RedisOptions](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.signalr.stackexchangeredis.redisoptions?view=aspnetcore-3.0)
-* [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration/blob/dev/README.md)
-* [Hosting ASP.NET Core images with Docker over HTTPS](https://docs.microsoft.com/en-us/aspnet/core/security/docker-https)
-* [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html)
-* [Aguafrommars/DynamicConfiguration](https://github.com/Aguafrommars/DynamicConfiguration)
-* [OpenTelemetry](https://opentelemetry.io/)
+[Doc](../../doc/SERVER.md)
\ No newline at end of file
diff --git a/src/Aguacongas.TheIdServer.Duende/SeedData.cs b/src/Aguacongas.TheIdServer.Duende/SeedData.cs
index 309456a9d..99b090881 100644
--- a/src/Aguacongas.TheIdServer.Duende/SeedData.cs
+++ b/src/Aguacongas.TheIdServer.Duende/SeedData.cs
@@ -4,28 +4,26 @@
using Aguacongas.IdentityServer.Store;
using Aguacongas.TheIdServer.Data;
using Aguacongas.TheIdServer.Models;
+using Duende.IdentityServer;
using IdentityModel;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
+using Serilog;
using System.Security.Claims;
using System.Text.Json;
-using System.Threading.Tasks;
using Entity = Aguacongas.IdentityServer.Store.Entity;
-using Duende.IdentityServer;
using ISModels = Duende.IdentityServer.Models;
namespace Aguacongas.TheIdServer
{
public static class SeedData
{
- public static void EnsureSeedData(IConfiguration configuration, IServiceProvider services)
+ private static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ };
+
+ public static void EnsureSeedData(IConfiguration configuration, IServiceProvider services)
{
using var scope = services.CreateScope();
@@ -38,7 +36,7 @@ public static void EnsureSeedData(IConfiguration configuration, IServiceProvider
var opContext = scope.ServiceProvider.GetRequiredService();
opContext.Database.Migrate();
- var appcontext = scope.ServiceProvider.GetService();
+ var appcontext = scope.ServiceProvider.GetRequiredService();
appcontext.Database.Migrate();
}
@@ -74,17 +72,17 @@ public static void SeedUsers(IServiceScope scope, IConfiguration configuration)
int index = 0;
foreach (var user in userList)
{
- var existing = userMgr.FindByNameAsync(user.UserName).GetAwaiter().GetResult();
+ var existing = userMgr.FindByNameAsync(user.UserName!).GetAwaiter().GetResult();
if (existing != null)
{
logger.LogInformation("{UserName} already exists", user.UserName);
continue;
}
var pwd = configuration.GetValue($"InitialData:Users:{index}:Password");
- ExcuteAndCheckResult(() => userMgr.CreateAsync(user, pwd))
+ ExcuteAndCheckResult(() => userMgr.CreateAsync(user, pwd!))
.GetAwaiter().GetResult();
- var claimList = configuration.GetSection($"InitialData:Users:{index}:Claims").Get>()
+ var claimList = configuration.GetSection($"InitialData:Users:{index}:Claims").Get>()!
.Select(c => new Claim(c.ClaimType, c.ClaimValue, c.OriginalType, c.Issuer))
.ToList();
claimList.Add(new Claim(JwtClaimTypes.UpdatedAt, DateTime.Now.ToEpochTime().ToString(), ClaimValueTypes.Integer64));
@@ -92,7 +90,7 @@ public static void SeedUsers(IServiceScope scope, IConfiguration configuration)
.GetAwaiter().GetResult();
var roleList = configuration.GetSection($"InitialData:Users:{index}:Roles").Get>();
- ExcuteAndCheckResult(() => userMgr.AddToRolesAsync(user, roleList))
+ ExcuteAndCheckResult(() => userMgr.AddToRolesAsync(user, roleList!))
.GetAwaiter().GetResult();
logger.LogInformation("{UserName} created", user.UserName);
@@ -155,12 +153,9 @@ private static void SeedCultureFile(IServiceProvider provider, string file)
}
var exsitings = culture.Resources.ToList();
- var resources = JsonSerializer.Deserialize>(File.ReadAllText(file), new JsonSerializerOptions
- {
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase
- });
+ var resources = JsonSerializer.Deserialize>(File.ReadAllText(file), _jsonSerializerOptions);
- foreach (var resource in resources)
+ foreach (var resource in resources!)
{
if (!exsitings.Any(r => r.Key == resource.Key))
{
@@ -533,7 +528,14 @@ private static void SeedClients(IConfiguration configuration, IServiceProvider p
SlidingRefreshTokenLifetime = client.SlidingRefreshTokenLifetime,
UpdateAccessTokenClaimsOnRefresh = client.UpdateAccessTokenClaimsOnRefresh,
UserCodeType = client.UserCodeType,
- UserSsoLifetime = client.UserSsoLifetime
+ UserSsoLifetime = client.UserSsoLifetime,
+ CibaLifetime = client.CibaLifetime,
+ CoordinateLifetimeWithUserSession = client.CoordinateLifetimeWithUserSession,
+ PollingInterval = client.PollingInterval,
+ RequireRequestObject = client.RequireRequestObject,
+ RequireDPoP = client.RequireDPoP,
+ PushedAuthorizationLifetime = client.PushedAuthorizationLifetime,
+ RequirePushedAuthorization = client.RequirePushedAuthorization
}).GetAwaiter().GetResult();
}
catch (ArgumentException)
diff --git a/src/Aguacongas.TheIdServer.Duende/Views/Account/LoggedOut.cshtml b/src/Aguacongas.TheIdServer.Duende/Views/Account/LoggedOut.cshtml
index 754ae4361..7478ed04f 100644
--- a/src/Aguacongas.TheIdServer.Duende/Views/Account/LoggedOut.cshtml
+++ b/src/Aguacongas.TheIdServer.Duende/Views/Account/LoggedOut.cshtml
@@ -20,7 +20,7 @@ Copyright (c) 2023 @Olivier Lefebvre
@if (Model.PostLogoutRedirectUri != null)
{
- @Localizer["Click here to return to the {1} application.", Model.PostLogoutRedirectUri, Model.ClientName]
+ @Localizer["Click here to return to the {1} application.", Model.PostLogoutRedirectUri, Model.ClientName!]