From 663acc682171a08b31546dc0bd47c1f498536aff Mon Sep 17 00:00:00 2001 From: RolandGuijt Date: Thu, 20 Jun 2024 16:26:19 +0200 Subject: [PATCH] Modernize code in data, diagnostics, fundamentals (#487) Co-authored-by: Roland Guijt --- .../v7/docs/content/data/configuration.md | 62 +++++++-------- IdentityServer/v7/docs/content/data/ef.md | 75 +++++++++---------- .../docs/content/data/operational/grants.md | 11 +-- .../v7/docs/content/data/operational/keys.md | 7 +- .../docs/content/data/operational/sessions.md | 32 +++----- .../v7/docs/content/diagnostics/events.md | 4 +- .../v7/docs/content/diagnostics/logging.md | 55 ++++---------- .../v7/docs/content/fundamentals/hosting.md | 18 ++--- .../keys/automatic_key_management.md | 4 +- .../content/fundamentals/keys/migration.md | 10 +-- .../keys/static_key_management.md | 22 +++--- .../docs/content/fundamentals/license_key.md | 10 +-- .../fundamentals/resources/api_scopes.md | 11 +-- 13 files changed, 129 insertions(+), 192 deletions(-) diff --git a/IdentityServer/v7/docs/content/data/configuration.md b/IdentityServer/v7/docs/content/data/configuration.md index 530464c6..d8a1c6b7 100644 --- a/IdentityServer/v7/docs/content/data/configuration.md +++ b/IdentityServer/v7/docs/content/data/configuration.md @@ -21,14 +21,12 @@ There are [convenience methods]({{}}) For example: ```cs -public void ConfigureServices(IServiceCollection services) -{ - services.AddIdentityServer() - .AddClientStore() - .AddCorsPolicyService() - .AddResourceStore() - .AddIdentityProviderStore(); -} +builder.Services.AddIdentityServer() + .AddClientStore() + .AddCorsPolicyService() + .AddResourceStore() + .AddIdentityProviderStore(); + ``` ## Caching Configuration Data @@ -41,38 +39,34 @@ The caching implementation relies upon an *ICache\* service and must also be For example: ```cs -public void ConfigureServices(IServiceCollection services) -{ - services.AddIdentityServer() - .AddClientStore() - .AddCorsPolicyService() - .AddResourceStore() - .AddInMemoryCaching() - .AddClientStoreCache() - .AddCorsPolicyCache() - .AddResourceStoreCache() - .AddIdentityProviderStoreCache(); -} +builder.Services.AddIdentityServer() + .AddClientStore() + .AddCorsPolicyService() + .AddResourceStore() + .AddInMemoryCaching() + .AddClientStoreCache() + .AddCorsPolicyCache() + .AddResourceStoreCache() + .AddIdentityProviderStoreCache(); + ``` The duration of the data in the default cache is configurable on the [IdentityServerOptions]({{}}). For example: ```cs -public void ConfigureServices(IServiceCollection services) -{ - services.AddIdentityServer(options => { - options.Caching.ClientStoreExpiration = TimeSpan.FromMinutes(5); - options.Caching.ResourceStoreExpiration = TimeSpan.FromMinutes(5); - }) - .AddClientStore() - .AddCorsPolicyService() - .AddResourceStore() - .AddInMemoryCaching() - .AddClientStoreCache() - .AddCorsPolicyCache() - .AddResourceStoreCache(); -} +builder.Services.AddIdentityServer(options => { + options.Caching.ClientStoreExpiration = TimeSpan.FromMinutes(5); + options.Caching.ResourceStoreExpiration = TimeSpan.FromMinutes(5); +}) + .AddClientStore() + .AddCorsPolicyService() + .AddResourceStore() + .AddInMemoryCaching() + .AddClientStoreCache() + .AddCorsPolicyCache() + .AddResourceStoreCache(); + ``` Further customization of the cache is possible: diff --git a/IdentityServer/v7/docs/content/data/ef.md b/IdentityServer/v7/docs/content/data/ef.md index cfc4e3a0..1abd5389 100644 --- a/IdentityServer/v7/docs/content/data/ef.md +++ b/IdentityServer/v7/docs/content/data/ef.md @@ -22,23 +22,20 @@ For storing [configuration data]({{}}), then the configur This support provides implementations of the *IClientStore*, *IResourceStore*, *IIdentityProviderStore*, and the *ICorsPolicyService* extensibility points. These implementations use a *DbContext*-derived class called *ConfigurationDbContext* to model the tables in the database. -To use the configuration store support, use the *AddConfigurationStore* extension method after the call to *AddIdentityServer*: +To use the configuration store support, in Program.cs use the *AddConfigurationStore* extension method after the call to *AddIdentityServer*: ```csharp -public IServiceProvider ConfigureServices(IServiceCollection services) -{ - const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=YourIdentityServerDatabase;trusted_connection=yes;"; - var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; - - services.AddIdentityServer() - // this adds the config data from DB (clients, resources, CORS) - .AddConfigurationStore(options => - { - options.ConfigureDbContext = builder => - builder.UseSqlServer(connectionString, - sql => sql.MigrationsAssembly(migrationsAssembly)); - }); -} +const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=YourIdentityServerDatabase;trusted_connection=yes;"; +var migrationsAssembly = typeof(Program).GetTypeInfo().Assembly.GetName().Name; + +builder.Services.AddIdentityServer() + // this adds the config data from DB (clients, resources, CORS) + .AddConfigurationStore(options => + { + options.ConfigureDbContext = builder => + builder.UseSqlServer(connectionString, + sql => sql.MigrationsAssembly(migrationsAssembly)); + }); ``` To configure the configuration store, use the *ConfigurationStoreOptions* options object passed to the configuration callback. @@ -70,13 +67,11 @@ options.ConfigureDbContext = b => To enable caching for the EF configuration store implementation, use the *AddConfigurationStoreCache* extension method: ```csharp -public IServiceProvider ConfigureServices(IServiceCollection services) -{ - services.AddIdentityServer() - .AddConfigurationStore(options => { ... }) - // this is something you will want in production to reduce load on and requests to the DB - .AddConfigurationStoreCache(); -} +builder.Services.AddIdentityServer() + .AddConfigurationStore(options => { ... }) + // this is something you will want in production to reduce load on and requests to the DB + .AddConfigurationStoreCache(); + ``` ## Operational Store @@ -84,27 +79,25 @@ For storing [operational data]({{}}) then the operational s This support provides implementations of the *IPersistedGrantStore*, *IDeviceFlowStore*, *IServerSideSessionStore*, and *ISigningKeyStore* extensibility points. The implementation uses a *DbContext*-derived class called *PersistedGrantDbContext* to model the table in the database. -To use the operational store support, use the *AddOperationalStore* extension method after the call to *AddIdentityServer*: +To use the operational store support, in Program.cs use the *AddOperationalStore* extension method after the call to *AddIdentityServer*: ```csharp -public IServiceProvider ConfigureServices(IServiceCollection services) -{ - const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=YourIdentityServerDatabase;trusted_connection=yes;"; - var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; - - services.AddIdentityServer() - // this adds the operational data from DB (codes, tokens, consents) - .AddOperationalStore(options => - { - options.ConfigureDbContext = builder => - builder.UseSqlServer(connectionString, - sql => sql.MigrationsAssembly(migrationsAssembly)); - - // this enables automatic token cleanup. this is optional. - options.EnableTokenCleanup = true; - options.TokenCleanupInterval = 3600; // interval in seconds (default is 3600) - }); -} +const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=YourIdentityServerDatabase;trusted_connection=yes;"; +var migrationsAssembly = typeof(Program).GetTypeInfo().Assembly.GetName().Name; + +builder.Services.AddIdentityServer() + // this adds the operational data from DB (codes, tokens, consents) + .AddOperationalStore(options => + { + options.ConfigureDbContext = builder => + builder.UseSqlServer(connectionString, + sql => sql.MigrationsAssembly(migrationsAssembly)); + + // this enables automatic token cleanup. this is optional. + options.EnableTokenCleanup = true; + options.TokenCleanupInterval = 3600; // interval in seconds (default is 3600) + }); + ``` To configure the operational store, use the *OperationalStoreOptions* options object passed to the configuration callback. diff --git a/IdentityServer/v7/docs/content/data/operational/grants.md b/IdentityServer/v7/docs/content/data/operational/grants.md index d685e4ca..a4178b62 100644 --- a/IdentityServer/v7/docs/content/data/operational/grants.md +++ b/IdentityServer/v7/docs/content/data/operational/grants.md @@ -18,13 +18,10 @@ Custom implementations of *IPersistedGrantStore*, and/or *IDeviceFlowStore* must For example: ```cs -public void ConfigureServices(IServiceCollection services) -{ - services.AddIdentityServer(); - - services.AddTransient(); - services.AddTransient(); -} +builder.Services.AddIdentityServer(); + +builder.Services.AddTransient(); +builder.Services.AddTransient(); ``` ## Grant Expiration and Consumption diff --git a/IdentityServer/v7/docs/content/data/operational/keys.md b/IdentityServer/v7/docs/content/data/operational/keys.md index d3a70a25..5dafe72c 100644 --- a/IdentityServer/v7/docs/content/data/operational/keys.md +++ b/IdentityServer/v7/docs/content/data/operational/keys.md @@ -15,11 +15,8 @@ To register a custom signing key store in the DI container, there is a *AddSigni For example: ```cs -public void ConfigureServices(IServiceCollection services) -{ - services.AddIdentityServer() - .AddSigningKeyStore(); -} +builder.Services.AddIdentityServer() + .AddSigningKeyStore(); ``` ## Key Lifecycle diff --git a/IdentityServer/v7/docs/content/data/operational/sessions.md b/IdentityServer/v7/docs/content/data/operational/sessions.md index 1181eb33..50558f5b 100644 --- a/IdentityServer/v7/docs/content/data/operational/sessions.md +++ b/IdentityServer/v7/docs/content/data/operational/sessions.md @@ -22,23 +22,17 @@ It is still necessary to call *AddServerSideSessions* to enable the server-side For example: ```cs -public void ConfigureServices(IServiceCollection services) -{ - services.AddIdentityServer() - .AddServerSideSessions() - .AddServerSideSessionStore(); -} +builder.Services.AddIdentityServer() + .AddServerSideSessions() + .AddServerSideSessionStore(); ``` There is also an overloaded version of a *AddServerSideSessions* that will perform both registration steps in one call. For example: ```cs -public void ConfigureServices(IServiceCollection services) -{ - services.AddIdentityServer() - .AddServerSideSessions(); -} +builder.Services.AddIdentityServer() + .AddServerSideSessions(); ``` ## EntityFramework store implementation @@ -50,13 +44,11 @@ For example: ```cs -public void ConfigureServices(IServiceCollection services) -{ - services.AddIdentityServer() - .AddServerSideSessions() - .AddOperationalStore(options => - { - // ... - }); -} +builder.Services.AddIdentityServer() + .AddServerSideSessions() + .AddOperationalStore(options => + { + // ... + }); + ``` diff --git a/IdentityServer/v7/docs/content/diagnostics/events.md b/IdentityServer/v7/docs/content/diagnostics/events.md index aee7065d..d013de30 100644 --- a/IdentityServer/v7/docs/content/diagnostics/events.md +++ b/IdentityServer/v7/docs/content/diagnostics/events.md @@ -11,10 +11,10 @@ This makes it easy to query and analyze them and extract useful information that Events work great with structured logging stores like [ELK](https://www.elastic.co/webinars/introduction-elk-stack), [Seq](https://getseq.net) or [Splunk](https://www.splunk.com/). ### Emitting events -Events are not turned on by default - but can be globally configured in the *ConfigureServices* method, e.g.: +Events are not turned on by default - but can be globally configured when *AddIdentityServer* is called, e.g.: ```cs -services.AddIdentityServer(options => +builder.Services.AddIdentityServer(options => { options.Events.RaiseSuccessEvents = true; options.Events.RaiseFailureEvents = true; diff --git a/IdentityServer/v7/docs/content/diagnostics/logging.md b/IdentityServer/v7/docs/content/diagnostics/logging.md index f6feb8df..037193e0 100644 --- a/IdentityServer/v7/docs/content/diagnostics/logging.md +++ b/IdentityServer/v7/docs/content/diagnostics/logging.md @@ -42,45 +42,18 @@ In production, logging might produce too much data. It is recommended you either We personally like [Serilog](https://serilog.net) and the [Serilog.AspNetCore](https://github.com/serilog/serilog-aspnetcore) package a lot. Give it a try: ```cs - public class Program - { - public static int Main(string[] args) - { - Activity.DefaultIdFormat = ActivityIdFormat.W3C; - - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) - .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) - .MinimumLevel.Override("System", LogEventLevel.Warning) - .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) - .Enrich.FromLogContext() - .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code) - .CreateLogger(); - - try - { - Log.Information("Starting host..."); - CreateHostBuilder(args).Build().Run(); - return 0; - } - catch (Exception ex) - { - Log.Fatal(ex, "Host terminated unexpectedly."); - return 1; - } - finally - { - Log.CloseAndFlush(); - } - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) - .UseSerilog() - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } +//Program.cs +Activity.DefaultIdFormat = ActivityIdFormat.W3C; + +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) + .MinimumLevel.Override("System", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code) + .CreateLogger(); + +builder.Logging.AddSeriLog(); ``` \ No newline at end of file diff --git a/IdentityServer/v7/docs/content/fundamentals/hosting.md b/IdentityServer/v7/docs/content/fundamentals/hosting.md index 455f362c..1fdbe893 100644 --- a/IdentityServer/v7/docs/content/fundamentals/hosting.md +++ b/IdentityServer/v7/docs/content/fundamentals/hosting.md @@ -10,13 +10,10 @@ While technically you could share the ASP.NET Core host between Duende IdentityS {{% /notice %}} ## DI system -You add the necessary services to the DI system by calling *AddIdentityServer* in your startup class: +You add the necessary services to the DI system by calling *AddIdentityServer* at application startup: ```cs -public void ConfigureServices(IServiceCollection services) -{ - var builder = services.AddIdentityServer(options => { ... }); -} +var idsvrBuilder = builder.Services.AddIdentityServer(options => { ... }); ``` Many of the fundamental configuration settings can be set on the options. See the *[IdentityServerOptions]({{< ref "/reference/options" >}})* reference for more details. @@ -25,7 +22,7 @@ The builder object has a number of extension methods to add additional services You can see the full list in the [reference]({{< ref "/reference/di" >}}) section, but very commonly you start by adding the configuration stores for clients and resources, e.g.: ```cs -var builder = services.AddIdentityServer() +var idsvrBuilder = builder.Services.AddIdentityServer() .AddInMemoryClients(Config.Clients) .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryApiScopes(Config.ApiScopes) @@ -41,19 +38,14 @@ Since ordering is important in the pipeline, you typically want to put the Ident This would be a very typical minimal pipeline: ```cs -public void Configure(IApplicationBuilder app) -{ app.UseStaticFiles(); app.UseRouting(); app.UseIdentityServer(); app.UseAuthorization(); - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute(); - }); -} + app.MapDefaultControllerRoute(); + ``` {{% notice note %}} diff --git a/IdentityServer/v7/docs/content/fundamentals/keys/automatic_key_management.md b/IdentityServer/v7/docs/content/fundamentals/keys/automatic_key_management.md index fac46b0a..12770ab2 100644 --- a/IdentityServer/v7/docs/content/fundamentals/keys/automatic_key_management.md +++ b/IdentityServer/v7/docs/content/fundamentals/keys/automatic_key_management.md @@ -38,7 +38,7 @@ when they are retired. All of these options are configurable in the *KeyManagement* options. For example: ```cs -var builder = services.AddIdentityServer(options => +var idsvrBuilder = builder.Services.AddIdentityServer(options => { // new key every 30 days options.KeyManagement.RotationInterval = TimeSpan.FromDays(30); @@ -73,7 +73,7 @@ If you are deploying in a load balanced environment and wish to use the access to the *KeyPath*. ```cs -var builder = services.AddIdentityServer(options => +var idsvrBuilder = builder.Services.AddIdentityServer(options => { // set path to store keys options.KeyManagement.KeyPath = "/home/shared/keys"; diff --git a/IdentityServer/v7/docs/content/fundamentals/keys/migration.md b/IdentityServer/v7/docs/content/fundamentals/keys/migration.md index fc7fe6dd..30a168d7 100644 --- a/IdentityServer/v7/docs/content/fundamentals/keys/migration.md +++ b/IdentityServer/v7/docs/content/fundamentals/keys/migration.md @@ -37,13 +37,13 @@ announced so that as client apps and APIs update their caches, they get the new key. IdentityServer will continue to sign keys with your old static key. ```cs -var builder = services.AddIdentityServer(options => +var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = true; }); var oldKey = LoadOldKeyFromVault(); -builder.AddSigningCredential(oldKey, SecurityAlgorithms.RsaSha256); +idsvrBuilder.AddSigningCredential(oldKey, SecurityAlgorithms.RsaSha256); ``` Wait until all APIs and applications have updated their signing key caches, and @@ -55,13 +55,13 @@ Next, switch to using the new automatically managed keys for signing, but still keep the old key for validation purposes. ```cs -var builder = services.AddIdentityServer(options => +var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = true; }); var oldKey = LoadOldKeyFromVault(); -builder.AddValidationKey(oldKey, SecurityAlgorithms.RsaSha256); +idsvrBuilder.AddValidationKey(oldKey, SecurityAlgorithms.RsaSha256); ``` Keep the old key as a validation key until all tokens signed with that key are @@ -71,7 +71,7 @@ expired, and then proceed to phase 3. Now the static key configuration can be removed entirely. ```cs -var builder = services.AddIdentityServer(options => +var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = true; }); diff --git a/IdentityServer/v7/docs/content/fundamentals/keys/static_key_management.md b/IdentityServer/v7/docs/content/fundamentals/keys/static_key_management.md index 40ba11e7..0c8d7e19 100644 --- a/IdentityServer/v7/docs/content/fundamentals/keys/static_key_management.md +++ b/IdentityServer/v7/docs/content/fundamentals/keys/static_key_management.md @@ -17,7 +17,7 @@ flag to *false* on the the *KeyManagement* property of [*IdentityServerOptions*]({{< ref "/reference/options#key-management" >}}): ```cs -var builder = services.AddIdentityServer(options => +var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = false; }); @@ -37,9 +37,9 @@ Signing keys are added with the [*AddSigningCredential*]({{< ref "/reference/di#signing-keys" >}}) configuration method: ```cs -var builder = services.AddIdentityServer(); +var idsvrBuilder = builder.Services.AddIdentityServer(); var key = LoadKeyFromVault(); // (Your code here) -builder.AddSigningCredential(key, SecurityAlgorithms.RsaSha256); +idsvrBuilder.AddSigningCredential(key, SecurityAlgorithms.RsaSha256); ``` You can call *AddSigningCredential* multiple times if you want to register more @@ -100,15 +100,15 @@ service. Configure IdentityServer for phase 1 by registering the new key as a validation key. ```cs -var builder = services.AddIdentityServer(options => +var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = false; }); var oldKey = LoadOldKeyFromVault(); var newKey = LoadNewKeyFromVault(); -builder.AddSigningCredential(oldKey, SecurityAlgorithms.RsaSha256); -builder.AddValidationKey(newKey, SecurityAlgorithms.RsaSha256) +idsvrBuilder.AddSigningCredential(oldKey, SecurityAlgorithms.RsaSha256); +idsvrBuilder.AddValidationKey(newKey, SecurityAlgorithms.RsaSha256) ``` Once IdentityServer is updated with the new key as a validation key, wait to @@ -128,15 +128,15 @@ be validated. The IdentityServer configuration change needed is simply to swap the signing credential and validation key. ```cs -var builder = services.AddIdentityServer(options => +var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = false; }); var oldKey = LoadOldKeyFromVault(); var newKey = LoadNewKeyFromVault(); -builder.AddSigningCredential(newKey, SecurityAlgorithms.RsaSha256); -builder.AddValidationKey(oldKey, SecurityAlgorithms.RsaSha256) +idsvrBuilder.AddSigningCredential(newKey, SecurityAlgorithms.RsaSha256); +idsvrBuilder.AddValidationKey(oldKey, SecurityAlgorithms.RsaSha256) ``` Again, you need to wait to proceed to phase 3. The delay here is typically @@ -150,12 +150,12 @@ Once enough time has passed that there are no unexpired tokens signed with the old key, it is safe to completely remove the old key. ```cs -var builder = services.AddIdentityServer(options => +var idsvrBuilder = builder.Services.AddIdentityServer(options => { options.KeyManagement.Enabled = false; }); var newKey = LoadNewKeyFromVault(); -builder.AddSigningCredential(newKey, SecurityAlgorithms.RsaSha256); +idsvrBuilder.AddSigningCredential(newKey, SecurityAlgorithms.RsaSha256); ``` diff --git a/IdentityServer/v7/docs/content/fundamentals/license_key.md b/IdentityServer/v7/docs/content/fundamentals/license_key.md index 7512f6d0..7c9aedf4 100644 --- a/IdentityServer/v7/docs/content/fundamentals/license_key.md +++ b/IdentityServer/v7/docs/content/fundamentals/license_key.md @@ -56,13 +56,11 @@ your IdentityServer, including the *LicenseKey*. Set the value of this property content of the license key file. ```csharp -public void ConfigureServices(IServiceCollection services) +builder.Services.AddIdentityServer(options => { - services.AddIdentityServer(options => - { - options.LicenseKey = "eyJhbG..."; // the content of the license key file - }); -} + options.LicenseKey = "eyJhbG..."; // the content of the license key file +}); + ``` ## License Validation and Logging diff --git a/IdentityServer/v7/docs/content/fundamentals/resources/api_scopes.md b/IdentityServer/v7/docs/content/fundamentals/resources/api_scopes.md index 28f3b997..25d7b6dd 100644 --- a/IdentityServer/v7/docs/content/fundamentals/resources/api_scopes.md +++ b/IdentityServer/v7/docs/content/fundamentals/resources/api_scopes.md @@ -82,11 +82,12 @@ You can add more identity information about the user to the access token. The additional claims added are based on the scope requested. The following scope definition tells the configuration system that when a *write* scope gets granted the *user_level* claim should be added to the access token: - var writeScope = new ApiScope( - name: "write", - displayName: "Write your data.", - userClaims: new[] { "user_level" }); - +```cs +var writeScope = new ApiScope( + name: "write", + displayName: "Write your data.", + userClaims: new[] { "user_level" }); +``` This will pass the *user_level* claim as a requested claim type to the profile service, so that the consumer of the access token can use this data as input for authorization decisions or business logic.