Skip to content

Add passthrough auth mode for gateway/proxy deployments#234

Closed
karimtabet wants to merge 2 commits intotableau:mainfrom
karimtabet:add-passthrough-auth-mode
Closed

Add passthrough auth mode for gateway/proxy deployments#234
karimtabet wants to merge 2 commits intotableau:mainfrom
karimtabet:add-passthrough-auth-mode

Conversation

@karimtabet
Copy link

@karimtabet karimtabet commented Feb 27, 2026

Summary

  • Add AUTH=passthrough mode that trusts pre-authenticated credentials from HTTP headers (Authorization: Bearer <token> + X-Tableau-User-Id: <user-luid>)
  • Enable enterprise gateway/proxy deployments where users authenticate via Kerberos, OIDC, or other mechanisms and the proxy forwards a valid Tableau REST API token
  • Reuse the existing restApi.setCredentials() code path (same as OAuth) — no new dependencies or Tableau API changes needed

Motivation

There is currently no way for a trusted upstream proxy to pass per-user credentials to the MCP server via HTTP headers. This is the standard pattern in enterprise gateway deployments where:

  • A reverse proxy authenticates users (via Kerberos, mTLS, OIDC, etc.)
  • The proxy acquires a Tableau-compatible token on behalf of the user
  • The proxy forwards the token to the MCP server in an HTTP header

This is particularly important for enterprises running Tableau Server versions that don't support OAuth (< 2025.3), where Kerberos SPNEGO authentication produces a workgroup_session_id that works as a standard REST API bearer token.

Deployment Auth Mode How Users Authenticate
Single user, CLI pat Static PAT in env vars
Multi-user, Tableau 2025.3+ oauth Browser redirect
Multi-user, Connected Apps direct-trust JWT via Connected App
Multi-user, enterprise gateway passthrough Proxy-provided token

Changes

  • src/config.ts: Add 'passthrough' to valid auth types, auto-detect transport as 'http', exempt from OAUTH_ISSUER requirement, validate transport must be HTTP
  • src/server/express.ts: Add getPassthroughAuthInfo() to extract credentials from request headers, add X-Tableau-User-Id to CORS allowed headers, wire up passthrough validation middleware
  • src/restApiInstance.ts: Check for pre-authenticated credentials (accessToken + userId) before the auth-type switch, skip sign-out for passthrough sessions
  • src/server/middleware.ts: Add validatePassthroughHeaders() middleware returning 401 JSON-RPC error on missing headers

Test plan

  • 7 new config tests: passthrough auth creation, transport auto-detection, stdio rejection, OAuth/PAT requirements relaxed, SERVER still required
  • 4 new middleware tests: 401 on missing Authorization, missing X-Tableau-User-Id, both missing; passes through when both present
  • 1 new restApiInstance test: verifies setCredentials called (not signIn), signOut skipped
  • Full test suite passes: 896 tests across 52 files, 0 failures
  • TypeScript compilation clean

Security considerations

  • AUTH=passthrough trusts the Authorization header completely — same trust model as a reverse proxy terminating TLS or Kerberos
  • The MCP server should NOT be exposed directly to end users in passthrough mode; it must sit behind a trusted proxy/gateway
  • This follows the standard pattern used by many enterprise services (Kubernetes API server, Envoy ext_authz, etc.)

Copilot AI review requested due to automatic review settings February 27, 2026 17:05
@salesforce-cla
Copy link

Thanks for the contribution! Before we can merge this, we need @karimtabet to sign the Salesforce Inc. Contributor License Agreement.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new passthrough authentication mode that enables the MCP server to work in enterprise gateway/proxy deployments where users are pre-authenticated by an upstream proxy. The proxy forwards valid Tableau REST API tokens via HTTP headers, and the MCP server trusts these credentials without performing additional authentication.

Changes:

  • Added passthrough as a valid auth type with automatic transport detection to HTTP
  • Implemented header-based credential extraction and validation middleware
  • Extended REST API credential handling to support pre-authenticated tokens for both OAuth and passthrough modes

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/config.ts Added 'passthrough' auth type, auto-detects transport as HTTP, exempts from OAUTH_ISSUER requirement, validates HTTP-only transport
src/config.test.ts Added 7 comprehensive tests for passthrough configuration validation
src/server/middleware.ts Added validatePassthroughHeaders middleware that returns 401 when required headers are missing
src/server/middleware.test.ts Added 4 tests covering all validation scenarios for passthrough headers
src/server/express.ts Added getPassthroughAuthInfo function, wired up middleware, added X-Tableau-User-Id to CORS headers
src/restApiInstance.ts Refactored to check for pre-authenticated credentials before auth-type switch, skip signOut for passthrough
src/restApiInstance.test.ts Added test verifying setCredentials usage for passthrough auth
src/testSetup.ts Added setCredentials mock to RestApi for test support

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Add a new `AUTH=passthrough` mode that allows a trusted upstream
proxy/gateway to pass pre-authenticated Tableau credentials via HTTP
headers (`Authorization: Bearer <token>` and `X-Tableau-User-Id`).

This fills a gap for enterprise deployments where Tableau Server doesn't
yet support OAuth but users already have authenticated sessions via
Kerberos or other mechanisms. The proxy acquires a Tableau REST API
token on behalf of the user and forwards it to the MCP server, which
uses it directly via `restApi.setCredentials()` — the same code path
OAuth already uses.

Changes:
- config.ts: Add 'passthrough' to valid auth types, auto-detect
  transport as 'http', skip OAUTH_ISSUER requirement, validate
  TRANSPORT must be 'http'
- server/express.ts: Extract credentials from Authorization and
  X-Tableau-User-Id headers, add headers to CORS allowedHeaders,
  wire up passthrough middleware
- restApiInstance.ts: Check for pre-authenticated credentials before
  auth-type switch (subsumes OAuth path), skip sign-out for passthrough
- server/middleware.ts: Add validatePassthroughHeaders middleware
  returning 401 on missing headers
- Tests: 12 new tests covering config validation, middleware header
  checks, and restApiInstance credential handling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@karimtabet karimtabet force-pushed the add-passthrough-auth-mode branch from 63c133c to 14b9f47 Compare February 27, 2026 17:11
@anyoung-tableau
Copy link
Collaborator

Hey @karimtabet , thanks for the PR! This is something we are working on internally, which is one of the reasons we request folks create an issue before opening a PR. The prototype branch is here: https://github.com/tableau/tableau-mcp/commits/anyoung/auth-tableau/ which is a bit behind the main branch, but the commits look pretty similar to your changes. We are also working on adding support for using Tableau as the OAuth authorization server which adds some additional complexity.

I'll discuss these changes with the team to ensure we want to support this scenario. I am inclined to bring my prototype branch up to speed and do some additional testing, ensuring it covers the use case described in this PR. Does that sound reasonable?

@karimtabet
Copy link
Author

Hey @anyoung-tableau thanks for the swift reply. Yep that sounds reasonable. I'm running with a fork atm in my production environment but, seeing how fast your project is moving, would rather use origin and not have too much drift. Any idea when we might get your branch rolled out?

@anyoung-tableau
Copy link
Collaborator

Hey @anyoung-tableau thanks for the swift reply. Yep that sounds reasonable. I'm running with a fork atm in my production environment but, seeing how fast your project is moving, would rather use origin and not have too much drift. Any idea when we might get your branch rolled out?

I'm going to look into it this week.

return undefined;
}

const authHeader = req.headers['authorization'];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@karimtabet would you be willing to pass the access token on the X-Tableau-Auth header instead of the Authorization header? The scenario we want to support is one where we don't introduce a new passthrough value for config.auth, but for requests that have the X-Tableau-Auth we ignore the normal auth mechanism and let the value of X-Tableau-Auth pass through. That way clients that don't provide the X-Tableau-Auth header will use PAT/Direct Trust/OAuth/etc and clients that do provide the header will use the provided access token. Thoughts?

Copy link
Collaborator

@anyoung-tableau anyoung-tableau Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @karimtabet , please let me know your thoughts, otherwise I will move forward with the X-Tableau-Auth header approach seen in #241

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I let karim know to have a look, your comments seem sensible

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @anyoung-tableau, sorry for the delay! I've updated the PR to use the X-Tableau-Auth header approach as you suggested — removed the dedicated passthrough auth type and replaced it with an ENABLE_PASSTHROUGH_AUTH flag that works alongside existing auth modes. Let me know if this aligns with what you had in mind, happy to adjust.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @karimtabet, last thing I wanted to check... are you already utilizing or planning to utilize these changes soon in some way? I'd like to move forward, but only if there's an active use case, otherwise it's idle code we need to maintain.

Copy link

@bbartels bbartels Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, we want to run the tableau in a multi-tenant way, where no interactive flow is necessary in order to auth with tableau. This is useful in scenarios where you have headless agents that you want to spin up at scale without user interaction. In our case we do something fairly complicated to fetch PAT's in a headless manner (fetch session token via kerberos, then use session token to create a pat). But this would be the last missing piece for us to run this as desired. The alternative is we would fork and create our own artefacts, but we operate in an airgapped environment so thats a lot trickier than it should be.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use this type of flow for all other mcps we deploy already, tableau being one of the ones that don't currently support passthrough flows

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks guys. Would you be willing to close this PR in favor of us moving forward with #241 instead?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine by me.

Replace dedicated 'passthrough' auth type with an ENABLE_PASSTHROUGH_AUTH
flag that works alongside existing auth modes. Requests with an
X-Tableau-Auth header (or workgroup_session_id cookie) bypass normal auth
and are validated against the Tableau REST API's /sessions/current endpoint,
with results cached.
@karimtabet karimtabet closed this Mar 13, 2026
@joeconstantino
Copy link
Contributor

Hey @karimtabet can you clarify the use case that you're building and why it needs this feature? Is it a dashboard extension?

@bbartels
Copy link

Hey @karimtabet can you clarify the use case that you're building and why it needs this feature? Is it a dashboard extension?

#234 (comment)

Some context here (i work with karim)

No dashoard extension, but seamless agent automations for BAs i.e. asking claude a question and letting it interact with tableau (mainly reading data, but also for dashboard creation)

@karimtabet
Copy link
Author

@JoeConstant we maintain a central MCP gateway for our users where we host the Tableau MCP. The use case is to be able to pass through a user's token to the remote MCP so that it can make API calls on the users behalf.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants