Configure platform identity
Stacklok Enterprise authenticates every request through your identity provider. Configure the issuer, audiences, scopes, and claims that the platform components validate in access tokens.
This page covers the Enterprise Manager, the console, and the Registry Server. Configure identity for individual MCP servers through enterprise authorization.
You need an OIDC-compatible identity provider where you can create authorization servers, OAuth client applications, custom scopes, and custom claims.
What each component expects
Each component validates a different slice of the access token. They share two requirements: the token is a signed JSON Web Token (JWT) so they can verify it locally against the identity provider's published key set, and the issuer matches the URL configured on the component. Beyond that, each component looks at a different combination of audience, scope, and claims.
| Component | What the component verifies |
|---|---|
| Enterprise Manager | Issuer, an audience that matches the configured value, and the configured scope is present |
| Console | Issuer and an audience that matches the console's OAuth client application |
| Registry Server | Issuer, the role claim that maps the user to a platform role, and the group claim used for bindings |
| MCP servers | Issuer and an audience that matches the server's MCPOIDCConfig, plus the role and group claims that enterprise authorization policies bind against |
The Stacklok CLI obtains tokens from the identity provider using the Proof Key for Code Exchange (PKCE) flow and presents them to each component. The console authenticates browser users through the authorization code flow on its server side. The components do not call the identity provider's introspection endpoint, so the authorization server you choose must issue signed JWTs rather than opaque tokens.
Pick a setup
Each token's audience must match the audience configured for its platform component. Use one of these identity provider setups.
One authorization server per component. Recommended where your identity provider supports it. Create one custom authorization server per platform component, give each its own audience, and point each component at its own issuer URL. Tokens issued for one component cannot be replayed against another, because their issuer values differ.
One authorization server with multiple audiences. Use this when your
identity provider plan caps you at a single custom authorization server. For
example, an Okta tenant on the free tier ships with one pre-provisioned
authorization server named default and does not let you create more. List
every component's audience on that one server. Each token still carries exactly
one audience, so a token minted for the Enterprise Manager is still rejected by
the console and vice versa.
Either setup produces JWTs the components accept. Pick based on what your identity provider plan supports, and use the worked example below as a template for either path.
Set up your identity provider (Okta example)
This walkthrough uses Okta with the second setup: a single authorization server
(default) carrying every component's audience. To use the first setup instead,
repeat steps 1 through 4 once per custom authorization server you create, and
point each component at the matching issuer URL in step 6.
Step 1: Add audiences to the authorization server
The Enterprise Manager and the console both reject tokens whose aud claim does
not match the value configured on the chart. Each component needs its audience
listed on the authorization server that issues its tokens.
In the Okta admin console, go to Security > API > Authorization
Servers and open the default server. On the Settings tab, add the
audiences your deployment uses:
enterprise-manager
cloud-ui
The exact strings here must match the audience value you set on each component
when you deploy it. Step 6 shows where that mapping goes in each component's
values.yaml.
Step 2: Add the scope the Enterprise Manager requires
The Enterprise Manager checks the access token for a scope before it returns
configuration to the client. Without a matching scope on the token, every config
request returns 401 Unauthorized. The console and Registry Server do not
require this scope.
On the Scopes tab, add a custom scope:
| Field | Value |
|---|---|
| Name | toolhive:config:read |
| Description | Read Stacklok configuration |
| Default scope | No |
Step 3: Add the claims your components map to platform roles
The Registry Server and the per-server MCP authorization policies both read group and role claims from the token to map each user to a platform role. The platform matches the claim names, while the values come from your identity provider's user profile. Adjust the expressions for your Okta tenant's group membership and role assignments.
On the Claims tab, add the claims each component reads:
| Name | Include in token type | Value | Value type | Include in |
|---|---|---|---|---|
groups | Access Token | user.groups | Expression | Any scope |
roles | Access Token | user.roles | Expression | Any scope |
email | Access Token | user.email | Expression | Any scope |
These are the IdP side of the claim setup. For how the platform maps these claim names to platform roles, and the ConfigMap that overrides them, see claim mapping in enterprise authorization.
Step 4: Add a default access policy
The Okta free tier's default authorization server requires an access policy
before client applications can request scopes. If your server already has an
applicable policy, continue to the next step.
On the Access Policies tab, add a default policy with one rule that permits your OAuth clients to request the audiences and scope from steps 1 and 2.
Step 5: Create OAuth client applications
Each Stacklok Enterprise component authenticates against a different OAuth client. The Stacklok CLI uses a native (PKCE) application that requests tokens for the Enterprise Manager audience. The console uses its own confidential (web) application, because it runs an authorization code flow on its server.
| Application | Type | Audience requested |
|---|---|---|
| Stacklok CLI | Native (PKCE, no secret) | enterprise-manager |
| Console | Web (confidential, secret) | cloud-ui |
Note the client IDs and, for the console, the client secret for the next step.
Step 6: Configure component chart values
Set the platform-wide issuer once. Configure the audience and scope for each component to match the values from steps 1 through 5.
Set the identity provider for the whole platform under global.stacklok:
global:
stacklok:
primaryIdp:
issuer: 'https://<TENANT>.okta.com/oauth2/default'
clientId: '<CONSOLE_CLIENT_ID>'
clientSecretRef:
name: '<SECRET_NAME>'
key: 'client-secret'
scopes: 'openid,profile,email'
Pre-create that Secret. There is no plaintext fallback for a corporate identity provider credential.
Then give the Enterprise Manager its own audience, scope, and client ID:
idpConfig:
audience: 'enterprise-manager'
requiredScope: 'toolhive:config:read'
idpType: 'okta'
clientID: '<STACKLOK_CLI_CLIENT_ID>'
Replace <TENANT> with your Okta subdomain and the <*_CLIENT_ID> placeholders
with the client IDs from step 5.
When deploying with the umbrella chart, keep global at the top level of your
values file. It is the one values tree every subchart can read, so a
global.stacklok.primaryIdp block nested under a component key is not read by
anything. Nest the Enterprise Manager block under the enterprise-manager: key.
The console needs no identity block of its own: it reads
global.stacklok.primaryIdp directly. See
Deploy the platform for the full
structure.
Verify a token
After a Stacklok client can sign in, decode its access token and verify the issuer, audience, and scope:
echo "<ACCESS_TOKEN>" | cut -d. -f2 | base64 -d 2>/dev/null | jq '.iss, .aud, .scp'
For a token that the Enterprise Manager would accept, you should see:
"https://<TENANT>.okta.com/oauth2/default"
"enterprise-manager"
["toolhive:config:read"]
If aud does not match the value you configured on the component, or scp does
not include toolhive:config:read for an Enterprise Manager request, the
component returns 401 Unauthorized.
Next steps
- Configure the Enterprise Manager with the issuer, audience, and scope from this page
- SCIM provisioning to populate the directory from the same identity provider
Troubleshooting
Tokens are opaque, not JWTs
Okta's org authorization server (the one without /oauth2/<server-name> in the
issuer URL) issues opaque tokens that require server-side introspection.
Stacklok Enterprise validates JWTs locally against the authorization server's
key set and does not call the introspection endpoint, so opaque tokens are not
usable. Use a custom authorization server (/oauth2/default or one you create)
so that access tokens are issued as JWTs.
aud is an array, not a string
Some identity providers put a single audience into an array
("aud": ["enterprise-manager"]). The components accept both forms. If
validation still fails, confirm the string inside the array matches the
configured audience exactly, including any prefix like api://.
scp is missing
Okta only includes the scope claim on access tokens when the OAuth client
requested it. Confirm the client's authorize request includes
scope=openid+toolhive:config:read (or the equivalent for your client library)
and that the access policy on the authorization server permits the scope for
that client.