Enabling OAuth (Tableau Server)
These docs are for enabling OAuth for Tableau Server only.
Tableau Cloud customers should refer to OAuth: Tableau Cloud.
A self-hosted MCP server can instead use your own identity provider with Tableau Server or Tableau Cloud, signing in with Direct Trust.
How to Enable OAuth
Tableau Server must be version 2025.3 or newer.
To enable OAuth, set the OAUTH_ISSUER environment variable to the origin of your
MCP server. When a URL for OAUTH_ISSUER is provided, the MCP server will act as an OAuth 2.1
resource server, capable of accepting and responding to protected resource requests using encrypted
access tokens.
When OAuth is enabled:
- MCP clients will be required to authenticate via Tableau OAuth before connecting to the MCP server
- The
TRANSPORTwill default tohttp(required for OAuth) - The
AUTHmethod will default tooauth
For more information, please see the MCP Authorization spec.
Use your own identity provider (self-hosted only)
A self-hosted MCP server can accept access tokens from your own OAuth or OpenID Connect identity provider (IdP) instead of Tableau. The MCP server checks each token, reads the Tableau username from one claim, and signs in to Tableau as that user with Direct Trust. This mode is not available for hosted Tableau MCP. It is an interim option for self-hosted deployments and may change or be removed in a later release.
To set it up:
- Set
OAUTH_ISSUERto your IdP's issuer andOAUTH_EMBEDDED_AUTHZ_SERVERtofalse. Tokens with any otherissare rejected. - Set
OAUTH_EXTERNAL_DISCOVERYtotrue(JWTs: access tokens or OpenID Connect ID tokens, with the keys read from your IdP's OpenID Connect discovery document). - Set
AUTHtodirect-trustwith{OAUTH_USERNAME}inJWT_SUB_CLAIM. - Have your IdP set the token's
audto this server's resource identifier: theOAUTH_RESOURCE_URIorigin with the/tableau-mcppath, or a value inOAUTH_GLOBAL_RESOURCE_URIS. Clients request it with theresourceparameter (RFC 8707). The MCP server accepts any token with a matchingaud, so use a value unique to this server. A client ID is fine only if it is this MCP server's own client ID and no other app receives tokens for it. Never use a client ID shared with another app, or a shared default audience such as Okta'sapi://default. - Grant the
tableau:mcp:*scopes your tools need in thescopeorscpclaim. The scopes also keep out tokens withouttableau:mcp:*scopes, including ID tokens and tokens meant for other apps. Accepting ID tokens requiresOAUTH_DISABLE_SCOPESset totrue. The audience is then the only check that ties a token to this server, so do this only when every accepted audience is unique to this server.
Pick a username claim that never changes and that users can't
edit, such as sub (the default). Avoid email or preferred_username unless your IdP guarantees
them: whoever controls the claim value signs in to Tableau as that user.
Also note:
- Passthrough auth (
ENABLE_PASSTHROUGH_AUTH) runs first. A request with a valid Tableau session is accepted without checking a bearer token. - The token's
client_id(orazp) claim is used as the client ID, which is sent to telemetry. - Each rejected token writes one
external-token-rejectedrecord to theauditlogger, whateverLOG_LEVELis set to.
Accept OpenID Connect ID tokens
If your IdP signs users in with OpenID Connect ID tokens instead of access tokens, the MCP server can accept those as the bearer token. The MCP server reads the keys from your IdP's discovery document:
OAUTH_ISSUER=https://idp.example.com
OAUTH_EMBEDDED_AUTHZ_SERVER=false
OAUTH_EXTERNAL_DISCOVERY=true
OAUTH_GLOBAL_RESOURCE_URIS=<this-mcp-server-client-id>
OAUTH_DISABLE_SCOPES=true
OAUTH_EXTERNAL_USERNAME_CLAIM_NAME=employee_id
Use this MCP server's own client ID only if no other app receives tokens for it: with scopes off, the audience is the only check that ties a token to this server.
On each request, the MCP server checks that:
- The token is signed with an asymmetric algorithm by a key from the discovery document's
jwks_uri. issequalsOAUTH_ISSUERexactly andaudis an accepted value.- The token has not expired (
exp) and is already valid (nbf, if present), allowing 5 seconds of clock skew. iss,sub,aud,exp, andiatare present.- The username claim is valid.
Results are not cached. A rejected token gets one generic 401 and an external-token-rejected
audit log record with the reason, never the token.
Environment Variables
The following environment variables configure OAuth behavior:
AUTH
The method the MCP server uses to authenticate to the Tableau REST APIs.
- Defaults to
oauthwhen OAuth is enabled. - Can still be set to other authentication methods. See Authentication for options.
- When set to a value other than
oauth, the MCP server will still be protected from unauthorized access by OAuth but will not use the Tableau session initiated by the Tableau OAuth flow to authenticate to the Tableau REST APIs. For example, ifAUTHis set touat, the MCP server will use the Unified Access Token (UAT) configuration to authenticate to the Tableau REST APIs.
OAUTH_ISSUER
Setting this environment variable enables OAuth. This should be the origin of your MCP server (the issuer of access tokens).
When you use your own identity provider, set it
to the provider's issuer exactly as it appears in the token's iss claim. A trailing slash matters.
With OAUTH_EXTERNAL_DISCOVERY, it must use https unless the host is
loopback.
- Example:
http://127.0.0.1:3927(for local testing) orhttps://tableau-mcp.example.com(for production) - Required if
AUTHisoauth - Required if
TRANSPORTishttpunless opted out withDANGEROUSLY_DISABLE_OAUTH
TRANSPORT
The MCP transport type to use for the server.
- Defaults to
httpwhen OAuth is enabled. - Must be
httpwhen OAuth is enabled.
SERVER
- When
AUTHisoauth, leave this empty to support any Tableau Cloud pod determined by the site the user signed into when connecting to the MCP server.
SITE_NAME
The target Tableau site for OAuth. The user must sign in to this site unless
OAUTH_LOCK_SITE is false.
- Use the site's Content URL (not the display name). You can find this in the site URL or in the Tableau Server/Cloud admin settings.
- The Content URL may differ from the display name (e.g., Content URL
Internalvs display name[INTERNAL] My Company). Either value will be accepted when verifying the site, but Content URL is recommended since it is used in Tableau's OAuth flow.
OAUTH_LOCK_SITE
Whether to require the user to sign in to the site specified in SITE_NAME when using
OAuth.
- Default:
true - When
true, the user must sign in to the site specified inSITE_NAME(or the Default site if empty).- If the user already has an active Tableau session in their browser for a different site, an error will be returned.
- When
false, the user can sign in to any site they can access.
OAUTH_REDIRECT_URI
The redirect URI for the OAuth flow.
- Default:
${OAUTH_ISSUER}/Callback - Recommended to not define a value at all and just rely on its default value.
- Path must be
/Callback(case-sensitive).
Tableau Server administrators must also use
tsm to set
oauth.allowed_redirect_uri_hosts to the host of the MCP server.
The value should be the same as OAUTH_ISSUER but without the protocol or any
trailing slash.
tsm configuration set -k oauth.allowed_redirect_uri_hosts -v tableau-mcp.example.com
tsm pending-changes apply
OAUTH_RESOURCE_URI
The base URL used in the resource field of the OAuth protected resource metadata document. Some
clients may require the resource field to match exactly with the URL used to access the MCP
server. This should be the base URL of your MCP server deployment.
This value is the deployment domain only (no path). The MCP server is reached at
<OAUTH_RESOURCE_URI>/tableau-mcp, which is the canonical resource identifier advertised as
resource in the protected resource metadata document. Incoming bearer tokens are
audience-validated against it: the token's aud claim must equal
<OAUTH_RESOURCE_URI>/tableau-mcp, per RFC 9068 and
RFC 8707. A token whose aud matches neither that value
nor OAUTH_GLOBAL_RESOURCE_URIS is rejected with a 401.
- Default:
http://127.0.0.1:3927 - Example:
http://127.0.0.1:3927(for local testing) orhttps://tableau-mcp.example.com(for production)
OAUTH_GLOBAL_RESOURCE_URIS
One or more additional values that are also accepted in a token's aud claim. Use this when clients
may reach the deployment through an environment-wide global URL in addition to the pod-specific
OAUTH_RESOURCE_URI.
- Optional. When unset, only the
OAUTH_RESOURCE_URIvalue is accepted. - Accepts a single value or a comma-separated list of values.
- Each value is matched against
aud, so set it to the value the authorization server stamps into the claim (for examplehttps://mcp.tableau.com). With your own identity provider, a value may be this MCP server's own client ID, to accept ID tokens. - Applies only when using a Tableau authorization server or
your own identity provider
(
OAUTH_EMBEDDED_AUTHZ_SERVERisfalse). - Each value must be unique to this MCP server: any token whose
audmatches is accepted.
OAUTH_CLIENT_ID_SECRET_PAIRS
A comma-separated list of client ID and secret pairs to be used for confidential OAuth clients that provide client credentials during token requests.
- Optional.
- Example:
client1:secret1,client2:secret2 - Client IDs and secrets must be unique and cannot contain colons or commas.
- The
/oauth2/tokenendpoint accepts client credentials in the request body or the authorization header. If both are provided, the request body takes precedence. - When unspecified, in the authorization server metadata:
- The
grant_types_supportedfield will not includeclient_credentials. - The
token_endpoint_auth_methods_supportedfield will not includeclient_secret_basicorclient_secret_post.
- The
Example /oauth2/token request body:
{
"grant_type": "client_credentials",
"client_id": "clientId",
"client_secret": "secret"
}
Example /oauth2/token request header:
Authorization: Basic Y2xpZW50SWQ6c2VjcmV0
Where Y2xpZW50SWQ6c2VjcmV0 is the base64 encoding of clientId:secret.
OAUTH_DISABLE_SCOPES
Disable scope enforcement and scope challenges.
- Default:
false - Useful for Tableau Server deployments that do not want to enforce scopes yet.
- When
true, the MCP server will not include scopes inWWW-Authenticatechallenges and will not enforce scopes on incoming access tokens. - With your own identity provider,
trueis required to accept ID tokens, which carry no scopes. It is safe only when every accepted audience is unique to this MCP server.
OAUTH_JWE_PRIVATE_KEY
The RSA private key used to decrypt the OAuth access token.
-
It or
OAUTH_JWE_PRIVATE_KEY_PATHmust be provided, but not both. -
Only PEM format is supported.
-
Examples:
-----BEGIN RSA PRIVATE KEY-----\nMIIE...HZ3Q==\n-----END RSA PRIVATE KEY-----or-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIJ...te1w==\n-----END ENCRYPTED PRIVATE KEY-----
The access token issued by the MCP server is encrypted using JWE (JSON Web Encryption) using an RSA public key. This public key is derived from the provided RSA private key.
MCP clients provide this encrypted access token to the MCP server on the Authorization header of
its requests. The MCP server decrypts the access token using the provided private key and uses the
Tableau access token held within to authenticate subsequent requests to Tableau APIs. Any requests
to the MCP server that do not have a valid access token will be rejected.
If you need a private key, you can generate one using openssl-genrsa e.g.
openssl genrsa -out private.pem
OAUTH_JWE_PRIVATE_KEY_PATH
The absolute path to the RSA private key (.pem) file used to decrypt the OAuth access token.
- It or
OAUTH_JWE_PRIVATE_KEYmust be provided, but not both. - Only PEM format is supported.
OAUTH_JWE_PRIVATE_KEY_PASSPHRASE
The passphrase for the private key if it is encrypted.
OAUTH_CIMD_DNS_SERVERS
The Tableau MCP server supports MCP clients that register using a Client ID Metadata Document (CIMD) URL. Part of this process requires resolving the IP address of the host of the document to protect against DNS rebinding and Server-Side Request Forgery (SSRF) attacks.
By default, the MCP server will use
Cloudflare's Public DNS (1.1.1.1 and
1.0.0.1) but you can override this using the OAUTH_CIMD_DNS_SERVERS environment variable.
- Default:
1.1.1.1,1.0.0.1 - Format is a comma-separated list of IP addresses.
- Example:
8.8.8.8,8.8.4.4(Google's Public DNS)
References:
OAUTH_EMBEDDED_AUTHZ_SERVER
A hint that OAUTH_ISSUER points at the embedded OAuth authorization server (authorize, token,
callback, register routes). When false, the issuer is an external authorization server (Tableau,
or your own identity provider); only
.well-known endpoints are exposed and JWE key/redirect URI constraints are skipped.
- Default:
true
OAUTH_EXTERNAL_DISCOVERY
When true, the MCP server reads your identity provider's
OpenID Connect discovery document at
OAUTH_ISSUER + /.well-known/openid-configuration (without a doubled / if the issuer ends in
one). It then accepts JWTs signed by a key from the document's jwks_uri: access tokens
(RFC 9068) or
OpenID Connect ID tokens. See
Use your own identity provider.
- Default:
false. Any value other thantrueleaves it off. IfOAUTH_EXTERNAL_USERNAME_CLAIM_NAMEis set withoutOAUTH_EXTERNAL_DISCOVERY=true, the server does not start. - Only RS, PS, ES, and EdDSA signatures are accepted. HS and
nonetokens are rejected. expandnbfallow 5 seconds of clock skew.iss,sub,aud,exp, andiatmust be present.- Keys are cached. They are fetched again when a token names an unknown key ID (
kid), at most once every 30 seconds. OAUTH_ISSUERmust usehttpsunless the host is loopback, and must not contain a username or password.- The document is read on the first request, then kept for the life of the process. If your provider
moves its
jwks_uri, restart the MCP server. If the read fails, the request is rejected and the next request tries again. - The document's
issuermust equalOAUTH_ISSUERexactly, including any trailing slash. jwks_urimust usehttpsunless both its host and theOAUTH_ISSUERhost are loopback. It can be on another host than the issuer.- Your provider must serve the document at that path.
OAUTH_EXTERNAL_USERNAME_CLAIM_NAME
The name of the token claim to use as the Tableau username. The claim's value must be a non-empty
string, or a whole number from 0 to 9007199254740991, which is used as its decimal string. sub
itself must be a non-empty string, so a number works only in a custom username claim. Surrounding
spaces in the name are trimmed, and an empty name falls back to sub.
- Default:
sub
SESSION_STORE_PROVIDER
The session store provider to use for the embedded OAuth authorization server's session state
(pending authorizations, authorization codes, refresh tokens, and OAuth client registrations).
Only relevant when AUTH is oauth with the embedded authorization server
enabled (the default; see OAUTH_EMBEDDED_AUTHZ_SERVER above).
- Default:
memory - Possible values:
memory- In-memory session store (default). State is process-local and does not survive a restart or a multi-replica deployment.custom- Load a custom session store provider from a user-specified module, backed by external infrastructure (e.g. Redis, a database, or blob storage) so session state survives restarts and is shared across replicas.
To use a custom session store provider, set SESSION_STORE_PROVIDER=custom and provide the module
path via SESSION_STORE_PROVIDER_CONFIG:
SESSION_STORE_PROVIDER=custom
SESSION_STORE_PROVIDER_CONFIG='{"module":"./my-session-store.js"}'
The custom provider module should export a default class or named export SessionStore that
implements the SessionStore
interface. consume must be implemented as a truly atomic operation against the backing store
(e.g. an ETag-based conditional write for S3/blob storage, a Lua script or MULTI/EXEC for
Redis, or a transaction for a relational store).
SESSION_STORE_PROVIDER_CONFIG
Configuration for custom session store providers (JSON string).
- Required when:
SESSION_STORE_PROVIDERiscustom - Format:
{"module": "<path-to-module>", ...additional-config}
The module field can be:
- A relative file path (e.g.,
./my-provider.js) - resolved from process working directory - An absolute file path (e.g.,
/path/to/provider.js) - An npm package name (e.g.,
@company/session-store-provider)
Example:
SESSION_STORE_PROVIDER_CONFIG='{"module":"./providers/redis-session-store.js"}'
ADVERTISE_API_SCOPES
Include Tableau API scopes in OAuth metadata and scope challenges.
- Default:
false - When
true,scopes_supportedincludes MCP + API scopes and scope challenges may include API scopes for step-up. - The check for AI-powered Pulse insight tools needs
tableau:entitlements:read, so it is part of the advertised set. Under strict scope matching (the default), tokens issued before upgrading must be re-consented to include it. WithDISABLE_STRICT_SCOPE_MATCHtrue, those tools are left out instead.
DISABLE_STRICT_SCOPE_MATCH
Do not require every access token to carry all supported scopes before it can connect. Allows for access tokens that are subsets of the full supported scopes lists to be used in a connection.
- Default:
false. - Has no effect when
OAUTH_DISABLE_SCOPESistrue. - When
false, a token missing any supportedtableau:mcp:*scope is rejected with a403insufficient_scopeerror. WhenADVERTISE_API_SCOPESistrue, the token must also carry the supported Tableau API scopes, except optional ones. - When
true, a token with only some of the supported scopes can connect. Tools the token lacks scopes for are left out of the tool list, and the server's initialize instructions name them. - When
true,ADVERTISE_API_SCOPESistrue, andENABLE_MCP_SITE_SETTINGSistrue(its default), every token must carrytableau:mcp_site_settings:readso the server can read the site's settings. A token without it is rejected with a403, and the embedded authorization server refuses to issue one (400invalid_scope).
OAUTH_AUTHORIZATION_CODE_TIMEOUT_MS
The timeout for the OAuth authorization codes.
- Default: 10 seconds.
- Max: 1 hour.
OAUTH_ACCESS_TOKEN_TIMEOUT_MS
The timeout for the OAuth access tokens.
- Default: 1 hour.
- Max: 30 days.
OAUTH_REFRESH_TOKEN_TIMEOUT_MS
OAuth support is currently in beta, and the present implementation retains refresh tokens in memory. Consequently, a server administrator's action of stopping and restarting the server, potentially during an upgrade, will result in the loss of all refresh tokens.
Any access tokens actively utilized by MCP clients will remain functional until their expiration, but subsequent attempts to refresh these tokens will fail, requiring the user to sign out and then sign back in.
This issue also pertains to authorization codes, though its impact is less likely due to their significantly shorter expiration window. This limitation will be addressed in a later release.
The timeout for the OAuth refresh tokens.
- Default: 30 days.
- Max: 1 year.
DANGEROUSLY_DISABLE_OAUTH
When TRANSPORT is http, the default behavior changes to require protecting your MCP server with
OAuth as a security best practice.
To opt out of this behavior at your own risk, set DANGEROUSLY_DISABLE_OAUTH to true.
This is not recommended as your MCP server will not be protected from unauthorized access. By explicitly disabling OAuth, you accept any and all risks associated with this decision.
- Default:
false
Sequence Diagram
Key Components
Grant Types
The MCP server supports three OAuth 2.1 grant types:
-
Authorization Code Grant (with PKCE): Interactive flow requiring user authentication
- User must authenticate with Tableau Server
- Supports refresh tokens for long-term access
- Uses PKCE for security
- Suitable for interactive applications
-
Client Credentials Grant: Non-interactive flow for service-to-service authentication
- No user authentication required
- No refresh tokens issued
- Uses pre-configured client ID/secret pairs
- Suitable for automated systems and APIs
-
Refresh Token Grant: Used to obtain new access tokens
- Only available for authorization code grants
- Automatically refreshes Tableau tokens when possible
- Falls back to existing tokens if refresh fails
Security Features
- PKCE (RFC 7636): Code challenge/verifier for public clients (authorization code only)
- JWE Encryption: Access tokens encrypted with RSA-OAEP-256/A256GCM
- State Validation: Prevents CSRF attacks (authorization code only)
- Time-limited Tokens: Authorization codes (10 seconds), access tokens (1 hour), refresh tokens (30 days)
- Client Credential Verification: Secure comparison using timing-safe operations
Token Types
- Tableau OAuth Tokens: Direct from Tableau Server (authorization code only)
- MCP Authorization Code: Short-lived code for token exchange (authorization code only)
- MCP Access Token: JWE-encrypted token containing Tableau credentials or client info
- MCP Refresh Token: For obtaining new access tokens (authorization code only)
Endpoints
/.well-known/oauth-protected-resource: Resource metadata discovery/.well-known/oauth-authorization-server: Authorization server metadata/oauth2/register: Dynamic client registration/oauth2/authorize: Authorization endpoint with PKCE (authorization code only)/Callback: OAuth callback handler (authorization code only)/oauth2/token: Token exchange and refresh (all grant types)/oauth2/revoke: Token revocation
For MCP client-facing token and consent cleanup, see the
reset-consent and
revoke-access-token tools. For full
cleanup, call reset-consent before revoke-access-token.