Skip to main content

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.

ComponentWhat the component verifies
Enterprise ManagerIssuer, an audience that matches the configured value, and the configured scope is present
ConsoleIssuer and an audience that matches the console's OAuth client application
Registry ServerIssuer, the role claim that maps the user to a platform role, and the group claim used for bindings
MCP serversIssuer 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:

Audiences
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:

FieldValue
Nametoolhive:config:read
DescriptionRead Stacklok configuration
Default scopeNo

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:

NameInclude in token typeValueValue typeInclude in
groupsAccess Tokenuser.groupsExpressionAny scope
rolesAccess Tokenuser.rolesExpressionAny scope
emailAccess Tokenuser.emailExpressionAny 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.

ApplicationTypeAudience requested
Stacklok CLINative (PKCE, no secret)enterprise-manager
ConsoleWeb (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:

values.yaml
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:

enterprise-manager values.yaml
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

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.