Client Scopes

A client scope decides what ends up inside a token. It groups protocol mappers, which are the rules that pull data off a user or a client and write it into the JWT as claims.

Scope types

TypeBehavior
DefaultIncluded in every token issued for the client
OptionalIncluded only when the client asks for it in the scope parameter
NoneNot assigned to the client at all

The same scope can be default for one client and optional for another. That is decided by the client scope mapping, not by the scope itself.

Scopes seeded by default

Every realm is seeded with a standard set of OIDC scopes, and every client in the realm gets them assigned.

ScopeAssigned asClaims it produces
openiddefaultnone of its own; marks the request as OIDC
profiledefaultgiven_name, family_name, preferred_username
emaildefaultemail, email_verified
rolesdefaultrealm_access.roles
offline_accessoptionalnone of its own
phoneoptionalphone_number
addressoptionaladdress

Protocol mappers

A mapper is identified by a mapper_type string. FerrisKey keeps the Keycloak names, so configuration written for Keycloak is recognizable here.

mapper_typeWhat it does
oidc-usermodel-property-mapperMaps a built-in user property (username, email, first name) to a claim
oidc-usermodel-attribute-mapperMaps a custom user attribute to a claim
oidc-usermodel-realm-role-mapperWrites the user’s realm roles into the token
oidc-usermodel-client-role-mapperWrites the user’s client roles into the token
oidc-group-membership-mapperWrites the user’s group memberships
oidc-organization-membership-mapperWrites the user’s organization memberships
oidc-organization-detail-mapperWrites details of the active organization
oidc-organization-role-mapperWrites the user’s roles within an organization
oidc-audience-mapperAdds a value to the aud claim
oidc-hardcoded-claim-mapperAdds a fixed value as a claim

Each mapper carries a JSON config. The keys follow the Keycloak convention:

{
  "user.attribute": "username",
  "claim.name": "preferred_username",
  "access.token.claim": "true",
  "id.token.claim": "true"
}

claim.name supports dotted paths, so realm_access.roles produces a nested object rather than a flat key with a dot in its name.

How scopes shape a token

graph LR
    U[User] --> A[Authenticate]
    A --> S[Resolve Scopes]
    S --> M[Run Protocol Mappers]
    M --> J[Build JWT Claims]
    J --> T[Sign Token]
  1. The user authenticates against a client.
  2. FerrisKey resolves which scopes apply: the client’s default scopes, plus any optional ones the request asked for.
  3. The mappers on each active scope run.
  4. Their output becomes JWT claims, alongside the standard ones.
  5. The token is signed with the realm’s key.

A scope is a claim contract

Read a scope as an agreement between the client and the authorization server: grant me profile, and I expect the user’s name and username to come back in the token.