Skip to content

UMA Resource Sharing

This example demonstrates UMA 2.0 (User-Managed Access) — an OAuth-based protocol for resource owner-controlled access. Two sample apps show different architecture patterns.

TIP

For UMA concepts and setup details, see the UMA 2.0 documentation.

Test Users

UsernamePasswordRole
alicealiceResource Owner — has full access to shared-document
bobbobRequesting Party — no access by default

Running

bash
dotnet run --project samples/UmaResourceSharing/AppHost

The Aspire dashboard opens automatically. Navigate to either app to interact with the UMA flow.

Blazor + Resource Server

Two-project setup: Blazor Server client communicates with a separate Minimal API resource server. The UmaTokenHandler transparently handles 401+UMA challenge-response between the two.

Resource Server

cs
using System.Security.Claims;
using Duende.AccessTokenManagement;
using Keycloak.AuthServices.Authentication;
using Keycloak.AuthServices.Authorization;
using Keycloak.AuthServices.Authorization.AuthorizationServer;
using Keycloak.AuthServices.Common;
using Keycloak.AuthServices.Sdk;
using Keycloak.AuthServices.Sdk.Protection;
using Keycloak.AuthServices.Sdk.Protection.Models;
using Keycloak.AuthServices.Sdk.Protection.Requests;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Options;

var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
var configuration = builder.Configuration;

builder.AddServiceDefaults();

var resourceServerClientId = "uma-resource-server";

services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddKeycloakWebApi(
        configuration,
        configureJwtBearerOptions: options =>
        {
            options.Audience = resourceServerClientId;
            options.RequireHttpsMetadata = false;
            options.MapInboundClaims = false;
        }
    );

services.AddAuthorization().AddKeycloakAuthorization().AddUmaPermissionTicketChallenge();

services.AddAuthorizationServer(configuration).AddStandardResilienceHandler();

var tokenClientName = ClientCredentialsClientName.Parse("uma_protection");

services.AddDistributedMemoryCache();
services
    .AddClientCredentialsTokenManagement()
    .AddClient(
        tokenClientName,
        client =>
        {
            var options = configuration.GetKeycloakOptions<KeycloakAuthenticationOptions>()!;
            client.ClientId = ClientId.Parse(options.Resource);
            client.ClientSecret = ClientSecret.Parse(options.Credentials.Secret);
            client.TokenEndpoint = new Uri(options.KeycloakTokenEndpoint);
        }
    );

services
    .AddKeycloakProtectionHttpClient(configuration)
    .AddClientCredentialsTokenHandler(tokenClientName);

services.AddProblemDetails();
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI();

app.UseExceptionHandler();
app.UseStatusCodePages();

app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/documents/{name}", (string name) => new DocumentResponse(name, $"Content of {name}"))
    .RequireProtectedResource("shared-document", "read");

app.MapGet(
        "/documents/{name}/details",
        (string name) => new DocumentResponse(name, $"Detailed content of {name}")
    )
    .RequireProtectedResource("shared-document", "write");

app.MapGet(
        "/documents",
        () => new[] { new DocumentResponse("shared-document", "A document shared via UMA") }
    )
    .RequireAuthorization();

app.MapPost(
        "/permissions/request",
        async (
            PermissionRequestBody request,
            ClaimsPrincipal user,
            IKeycloakProtectionClient protectionClient,
            IOptions<KeycloakAuthorizationServerOptions> authzOptions
        ) =>
        {
            var realm = authzOptions.Value.Realm;
            var userId = user.FindFirstValue("sub");
            if (string.IsNullOrEmpty(userId))
            {
                return Results.Unauthorized();
            }

            var resourceIds = await protectionClient.GetResourcesIdsAsync(
                realm,
                new GetResourcesRequestParameters { Name = request.Resource, ExactName = true }
            );

            if (resourceIds.Count == 0)
            {
                return Results.NotFound(new { Error = $"Resource '{request.Resource}' not found" });
            }

            var resourceId = resourceIds[0];

            // Check if a pending ticket already exists for this user/resource/scope
            var existing = await protectionClient.GetPermissionTicketsAsync(
                realm,
                new GetPermissionTicketsRequestParameters
                {
                    ResourceId = resourceId,
                    Requester = userId,
                }
            );

            var requestedScopes = request.Scopes ?? ["read"];
            var existingScopes = existing.Select(t => t.ScopeName ?? t.Scope).ToHashSet();

            var created = new List<string>();
            foreach (var scope in requestedScopes)
            {
                if (existingScopes.Contains(scope))
                {
                    continue;
                }

                var ticket = new PermissionTicket
                {
                    Resource = resourceId,
                    Requester = userId,
                    ScopeName = scope,
                    Granted = false,
                };

                // Create via the Permission Ticket API (POST /permission/ticket)
                var response = await protectionClient.CreatePermissionTicketWithResponseAsync(
                    realm,
                    ticket
                );

                if (response.IsSuccessStatusCode)
                {
                    created.Add(scope);
                }
            }

            return Results.Ok(
                new
                {
                    Message = $"Access requested for [{string.Join(", ", created)}] on '{request.Resource}'",
                    Scopes = created,
                }
            );
        }
    )
    .RequireAuthorization();

app.MapGet(
        "/permissions/pending",
        async (
            IKeycloakProtectionClient protectionClient,
            IOptions<KeycloakAuthorizationServerOptions> authzOptions
        ) =>
        {
            var realm = authzOptions.Value.Realm;

            var tickets = await protectionClient.GetPermissionTicketsAsync(
                realm,
                new GetPermissionTicketsRequestParameters { Granted = false, ReturnNames = true }
            );

            return Results.Ok(tickets);
        }
    )
    .RequireAuthorization();

app.MapPut(
        "/permissions/{id}/approve",
        async (
            string id,
            IKeycloakProtectionClient protectionClient,
            IOptions<KeycloakAuthorizationServerOptions> authzOptions
        ) =>
        {
            var realm = authzOptions.Value.Realm;

            var ticket = new PermissionTicket { Id = id, Granted = true };
            await protectionClient.UpdatePermissionTicketAsync(realm, ticket);

            return Results.NoContent();
        }
    )
    .RequireAuthorization();

app.MapDelete(
        "/permissions/{id}",
        async (
            string id,
            IKeycloakProtectionClient protectionClient,
            IOptions<KeycloakAuthorizationServerOptions> authzOptions
        ) =>
        {
            var realm = authzOptions.Value.Realm;
            await protectionClient.DeletePermissionTicketAsync(realm, id);

            return Results.NoContent();
        }
    )
    .RequireAuthorization();

app.MapGet(
        "/me",
        (ClaimsPrincipal user) =>
            new
            {
                Name = user.Identity?.Name,
                Claims = user.Claims.Select(c => new { c.Type, c.Value }),
            }
    )
    .RequireAuthorization();

app.MapDefaultEndpoints();

app.Run();

internal record DocumentResponse(string Name, string Content);

internal record PermissionRequestBody(string Resource, string[]? Scopes);

Client App

cs
using Keycloak.AuthServices.Authentication;
using Keycloak.AuthServices.Sdk;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using MudBlazor.Services;

var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
var configuration = builder.Configuration;

builder.AddServiceDefaults();

services
    .AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddKeycloakWebApp(
        configuration.GetSection(KeycloakAuthenticationOptions.Section),
        configureOpenIdConnectOptions: options =>
        {
            options.SaveTokens = true;
            options.ResponseType = OpenIdConnectResponseType.Code;
            options.RequireHttpsMetadata = false;
            options.Scope.Add("openid");
            options.Scope.Add("profile");
            options.Scope.Add("email");
        }
    );

services.AddAuthorization();
services.AddCascadingAuthenticationState();

services.AddMudServices();

services.AddHttpContextAccessor();
services.AddKeycloakUmaTicketExchangeHttpClient(configuration);
services
    .AddHttpClient(
        "ResourceServer",
        (sp, client) =>
        {
            var config = sp.GetRequiredService<IConfiguration>();
            var baseUrl =
                config["services:resource-server:http:0"]
                ?? config["ResourceServer:BaseUrl"]
                ?? "http://localhost:5180";
            client.BaseAddress = new Uri(baseUrl);
        }
    )
    .AddUmaTokenHandler();

services.AddRazorComponents().AddInteractiveServerComponents();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStaticFiles();
app.UseAntiforgery();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorComponents<ClientApp.Components.App>().AddInteractiveServerRenderMode();

app.MapGroup("/authentication").MapLoginAndLogout();

app.MapDefaultEndpoints();

app.Run();

Demo Walkthrough

  1. alice (owner) — Login → Documents → Access (read) → UMA Challenge → RPT → Access Granted
  2. bob (denied) — Login → Documents → Access (read) → UMA Challenge → Access Denied
  3. bob requests access → Request Access (read) → "Request submitted" → alice logs in → Approves → bob retries → Access Granted

See sample source code: samples/UmaResourceSharing/ClientApp

Razor Pages (Self-Contained)

Single-project setup: the Razor Pages app is the resource server. Authorization checks and permission ticket management happen inline — no separate API, no UmaTokenHandler.

Program.cs

cs
using Duende.AccessTokenManagement;
using Keycloak.AuthServices.Authentication;
using Keycloak.AuthServices.Authorization;
using Keycloak.AuthServices.Common;
using Keycloak.AuthServices.Sdk;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
var configuration = builder.Configuration;

builder.AddServiceDefaults();

services
    .AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddKeycloakWebApp(
        configuration.GetSection(KeycloakAuthenticationOptions.Section),
        configureOpenIdConnectOptions: options =>
        {
            options.SaveTokens = true;
            options.ResponseType = OpenIdConnectResponseType.Code;
            options.RequireHttpsMetadata = false;
            options.MapInboundClaims = false;
            options.Scope.Add("openid");
            options.Scope.Add("profile");
            options.Scope.Add("email");
        }
    );

var resourceServerClientId = "uma-resource-server";
var resourceServerSecret = "uma-resource-server-secret";

services
    .AddAuthorization()
    .AddKeycloakAuthorization()
    .AddAuthorizationBuilder()
    .AddPolicy("UmaRead", policy => policy.RequireProtectedResource("shared-document", "read"))
    .AddPolicy("UmaWrite", policy => policy.RequireProtectedResource("shared-document", "write"));

services
    .AddAuthorizationServer(options =>
    {
        configuration
            .GetSection(KeycloakAuthenticationOptions.Section)
            .BindKeycloakOptions(options);
        options.Resource = resourceServerClientId;
        options.Credentials = new() { Secret = resourceServerSecret };
        options.VerifyTokenAudience = true;
    })
    .AddStandardResilienceHandler();

var tokenClientName = ClientCredentialsClientName.Parse("uma_protection");

services.AddDistributedMemoryCache();
services
    .AddClientCredentialsTokenManagement()
    .AddClient(
        tokenClientName,
        client =>
        {
            var keycloakOptions =
                configuration.GetKeycloakOptions<KeycloakAuthenticationOptions>()!;
            client.ClientId = ClientId.Parse(resourceServerClientId);
            client.ClientSecret = ClientSecret.Parse(resourceServerSecret);
            client.TokenEndpoint = new Uri(keycloakOptions.KeycloakTokenEndpoint);
        }
    );

services
    .AddKeycloakProtectionHttpClient(options =>
    {
        configuration
            .GetSection(KeycloakAuthenticationOptions.Section)
            .BindKeycloakOptions(options);
        options.Resource = resourceServerClientId;
        options.Credentials = new() { Secret = resourceServerSecret };
    })
    .AddClientCredentialsTokenHandler(tokenClientName);

services.AddHttpContextAccessor();
services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();

app.MapGroup("/authentication").MapLoginAndLogout();

app.MapDefaultEndpoints();

app.Run();

Protected Page — Programmatic Authorization Check

cs
namespace RazorPagesApp.Pages.Documents;

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

[Authorize]
public class DetailsModel(IAuthorizationService authorizationService) : PageModel
{
    public string DocumentName { get; set; } = default!;
    public string Scope { get; set; } = default!;
    public string? Content { get; set; }
    public bool AccessGranted { get; set; }

    public async Task OnGetAsync(string name, string scope = "read")
    {
        this.DocumentName = name;
        this.Scope = scope;

        var policyName = scope == "write" ? "UmaWrite" : "UmaRead";
        var result = await authorizationService.AuthorizeAsync(this.User, policyName);

        if (result.Succeeded)
        {
            this.AccessGranted = true;
            this.Content = scope == "write" ? $"Detailed content of {name}" : $"Content of {name}";
        }
    }
}

Permission Request — Direct Protection API Usage

cs
namespace RazorPagesApp.Pages.Documents;

using System.Security.Claims;
using Keycloak.AuthServices.Authorization.AuthorizationServer;
using Keycloak.AuthServices.Sdk.Protection;
using Keycloak.AuthServices.Sdk.Protection.Models;
using Keycloak.AuthServices.Sdk.Protection.Requests;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;

[Authorize]
public class IndexModel(
    IKeycloakProtectionClient protectionClient,
    IOptions<KeycloakAuthorizationServerOptions> authzOptions
) : PageModel
{
    public string? Message { get; set; }
    public bool IsError { get; set; }

    public async Task<IActionResult> OnPostRequestAccessAsync(string scope)
    {
        var realm = authzOptions.Value.Realm;
        var userId = this.User.FindFirstValue("sub");
        if (string.IsNullOrEmpty(userId))
        {
            this.Message = "Unable to determine user identity.";
            this.IsError = true;
            return this.Page();
        }

        var resourceIds = await protectionClient.GetResourcesIdsAsync(
            realm,
            new GetResourcesRequestParameters { Name = "shared-document", ExactName = true }
        );

        if (resourceIds.Count == 0)
        {
            this.Message = "Resource 'shared-document' not found.";
            this.IsError = true;
            return this.Page();
        }

        var resourceId = resourceIds[0];

        var existing = await protectionClient.GetPermissionTicketsAsync(
            realm,
            new GetPermissionTicketsRequestParameters
            {
                ResourceId = resourceId,
                Requester = userId,
            }
        );

        var existingScopes = existing.Select(t => t.ScopeName ?? t.Scope).ToHashSet();

        if (existingScopes.Contains(scope))
        {
            this.Message = $"A request for '{scope}' scope already exists.";
            return this.Page();
        }

        var ticket = new PermissionTicket
        {
            Resource = resourceId,
            Requester = userId,
            ScopeName = scope,
            Granted = false,
        };

        var response = await protectionClient.CreatePermissionTicketWithResponseAsync(
            realm,
            ticket
        );

        if (response.IsSuccessStatusCode)
        {
            this.Message =
                $"Access request for '{scope}' submitted. The resource owner will review your request.";
        }
        else
        {
            this.Message = $"Failed to submit access request ({(int)response.StatusCode}).";
            this.IsError = true;
        }

        return this.Page();
    }
}

Permission Management — Approve / Deny

cs
namespace RazorPagesApp.Pages.Permissions;

using Keycloak.AuthServices.Authorization.AuthorizationServer;
using Keycloak.AuthServices.Sdk.Protection;
using Keycloak.AuthServices.Sdk.Protection.Models;
using Keycloak.AuthServices.Sdk.Protection.Requests;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;

[Authorize]
public class IndexModel(
    IKeycloakProtectionClient protectionClient,
    IOptions<KeycloakAuthorizationServerOptions> authzOptions
) : PageModel
{
    public IList<PermissionTicket> Tickets { get; set; } = [];
    public string? Message { get; set; }
    public bool IsError { get; set; }

    public async Task OnGetAsync()
    {
        await this.LoadTicketsAsync();
    }

    public async Task<IActionResult> OnPostApproveAsync(string ticketId)
    {
        var realm = authzOptions.Value.Realm;
        var ticket = new PermissionTicket { Id = ticketId, Granted = true };

        try
        {
            await protectionClient.UpdatePermissionTicketAsync(realm, ticket);
            this.Message = "Permission approved.";
        }
        catch
        {
            this.Message = "Failed to approve permission.";
            this.IsError = true;
        }

        await this.LoadTicketsAsync();
        return this.Page();
    }

    public async Task<IActionResult> OnPostDenyAsync(string ticketId)
    {
        var realm = authzOptions.Value.Realm;

        try
        {
            await protectionClient.DeletePermissionTicketAsync(realm, ticketId);
            this.Message = "Permission request denied.";
        }
        catch
        {
            this.Message = "Failed to deny permission.";
            this.IsError = true;
        }

        await this.LoadTicketsAsync();
        return this.Page();
    }

    private async Task LoadTicketsAsync()
    {
        var realm = authzOptions.Value.Realm;
        this.Tickets = await protectionClient.GetPermissionTicketsAsync(
            realm,
            new GetPermissionTicketsRequestParameters { Granted = false, ReturnNames = true }
        );
    }
}

Demo Walkthrough

  1. alice (owner) — Login → Documents → Access (read) → Content displayed
  2. bob (denied) — Login → Documents → Access (read) → "Access Denied" page
  3. bob requests access → Request Access (read) → "Request submitted" → alice logs in → Permissions → Approve → bob retries → Content displayed

See sample source code: samples/UmaResourceSharing/RazorPagesApp

Keycloak Realm Configuration
json
{
  "realm" : "Test",
  "enabled" : true,
  "sslRequired" : "none",
  "registrationAllowed" : false,
  "loginWithEmailAllowed" : true,
  "duplicateEmailsAllowed" : false,
  "resetPasswordAllowed" : false,
  "editUsernameAllowed" : false,
  "bruteForceProtected" : false,
  "roles" : {
    "realm" : [ {
      "id" : "f1a1b1c1-0001-0001-0001-000000000001",
      "name" : "default-roles-test",
      "composite" : true,
      "composites" : {
        "realm" : [ "offline_access", "uma_authorization" ],
        "client" : {
          "account" : [ "manage-account", "view-profile" ]
        }
      }
    }, {
      "id" : "f1a1b1c1-0001-0001-0001-000000000002",
      "name" : "offline_access",
      "description" : "${role_offline-access}"
    }, {
      "id" : "f1a1b1c1-0001-0001-0001-000000000003",
      "name" : "uma_authorization",
      "description" : "${role_uma_authorization}"
    } ]
  },
  "defaultRole" : {
    "id" : "f1a1b1c1-0001-0001-0001-000000000001",
    "name" : "default-roles-test",
    "composite" : true
  },
  "clients" : [ {
    "id" : "a1000001-0000-0000-0000-000000000001",
    "clientId" : "uma-resource-server",
    "name" : "UMA Resource Server",
    "description" : "Resource server with UMA protection and authorization services",
    "enabled" : true,
    "clientAuthenticatorType" : "client-secret",
    "secret" : "uma-resource-server-secret",
    "redirectUris" : [ "*" ],
    "webOrigins" : [ "*" ],
    "bearerOnly" : false,
    "consentRequired" : false,
    "standardFlowEnabled" : true,
    "implicitFlowEnabled" : false,
    "directAccessGrantsEnabled" : true,
    "serviceAccountsEnabled" : true,
    "authorizationServicesEnabled" : true,
    "publicClient" : false,
    "frontchannelLogout" : true,
    "protocol" : "openid-connect",
    "attributes" : {
      "oidc.ciba.grant.enabled" : "false",
      "backchannel.logout.session.required" : "true",
      "oauth2.device.authorization.grant.enabled" : "false",
      "display.on.consent.screen" : "false",
      "backchannel.logout.revoke.offline.tokens" : "false"
    },
    "fullScopeAllowed" : true,
    "nodeReRegistrationTimeout" : -1,
    "protocolMappers" : [ {
      "id" : "b1000001-0000-0000-0000-000000000001",
      "name" : "audience",
      "protocol" : "openid-connect",
      "protocolMapper" : "oidc-audience-mapper",
      "consentRequired" : false,
      "config" : {
        "included.client.audience" : "uma-resource-server",
        "id.token.claim" : "false",
        "lightweight.claim" : "false",
        "introspection.token.claim" : "true",
        "access.token.claim" : "true"
      }
    } ],
    "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ],
    "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ],
    "authorizationSettings" : {
      "allowRemoteResourceManagement" : true,
      "policyEnforcementMode" : "ENFORCING",
      "resources" : [ {
        "name" : "shared-document",
        "type" : "urn:documents",
        "ownerManagedAccess" : true,
        "displayName" : "Shared Document",
        "_id" : "c1000001-0000-0000-0000-000000000001",
        "uris" : [ ],
        "scopes" : [ {
          "name" : "read"
        }, {
          "name" : "write"
        } ]
      } ],
      "policies" : [ {
        "id" : "d1000001-0000-0000-0000-000000000010",
        "name" : "owner-policy",
        "description" : "Grants access to alice (resource owner)",
        "type" : "user",
        "logic" : "POSITIVE",
        "decisionStrategy" : "UNANIMOUS",
        "config" : {
          "users" : "[\"e1000001-0000-0000-0000-000000000001\"]"
        }
      }, {
        "id" : "d1000001-0000-0000-0000-000000000011",
        "name" : "owner-read-permission",
        "type" : "scope",
        "logic" : "POSITIVE",
        "decisionStrategy" : "UNANIMOUS",
        "config" : {
          "resources" : "[\"c1000001-0000-0000-0000-000000000001\"]",
          "scopes" : "[\"d1000001-0000-0000-0000-000000000001\"]",
          "applyPolicies" : "[\"owner-policy\"]"
        }
      }, {
        "id" : "d1000001-0000-0000-0000-000000000012",
        "name" : "owner-write-permission",
        "type" : "scope",
        "logic" : "POSITIVE",
        "decisionStrategy" : "UNANIMOUS",
        "config" : {
          "resources" : "[\"c1000001-0000-0000-0000-000000000001\"]",
          "scopes" : "[\"d1000001-0000-0000-0000-000000000002\"]",
          "applyPolicies" : "[\"owner-policy\"]"
        }
      } ],
      "scopes" : [ {
        "id" : "d1000001-0000-0000-0000-000000000001",
        "name" : "read"
      }, {
        "id" : "d1000001-0000-0000-0000-000000000002",
        "name" : "write"
      } ],
      "decisionStrategy" : "AFFIRMATIVE"
    }
  }, {
    "id" : "a1000001-0000-0000-0000-000000000002",
    "clientId" : "uma-client-app",
    "name" : "UMA Client App",
    "description" : "Blazor Server web app for UMA resource sharing",
    "enabled" : true,
    "clientAuthenticatorType" : "client-secret",
    "secret" : "uma-client-app-secret",
    "redirectUris" : [ "*" ],
    "webOrigins" : [ "*" ],
    "bearerOnly" : false,
    "consentRequired" : false,
    "standardFlowEnabled" : true,
    "implicitFlowEnabled" : false,
    "directAccessGrantsEnabled" : true,
    "serviceAccountsEnabled" : false,
    "authorizationServicesEnabled" : false,
    "publicClient" : false,
    "frontchannelLogout" : true,
    "protocol" : "openid-connect",
    "attributes" : {
      "oidc.ciba.grant.enabled" : "false",
      "backchannel.logout.session.required" : "true",
      "oauth2.device.authorization.grant.enabled" : "false",
      "display.on.consent.screen" : "false",
      "backchannel.logout.revoke.offline.tokens" : "false"
    },
    "fullScopeAllowed" : true,
    "nodeReRegistrationTimeout" : -1,
    "protocolMappers" : [ {
      "id" : "b1000002-0000-0000-0000-000000000001",
      "name" : "audience",
      "protocol" : "openid-connect",
      "protocolMapper" : "oidc-audience-mapper",
      "consentRequired" : false,
      "config" : {
        "included.client.audience" : "uma-resource-server",
        "id.token.claim" : "false",
        "lightweight.claim" : "false",
        "introspection.token.claim" : "true",
        "access.token.claim" : "true"
      }
    }, {
      "id" : "b1000002-0000-0000-0000-000000000002",
      "name" : "subject",
      "protocol" : "openid-connect",
      "protocolMapper" : "oidc-sub-mapper",
      "consentRequired" : false,
      "config" : {
        "introspection.token.claim" : "true",
        "access.token.claim" : "true"
      }
    } ],
    "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email", "basic" ],
    "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
  } ]
}