$auth.
There’s no proxy to run and no token in the URL. The connection URL stays the same, and
the client handles discovery, registration, sign-in and token refresh on its own.
Choose a mode
Pick Hosted if your users already sign in to your Xano backend with your own login
endpoints. Pick External if they sign in through an identity provider that issues JWT
access tokens.
Want to run the OAuth front end yourself instead, for example because your provider issues
opaque tokens or you need sign-up on first login? See Bring your own identity
provider. That approach needs sign-in off on the
server.
What changes when you turn it on
- Access tokens work only on this MCP server. A token issued for it is refused by your regular API endpoints and by every other MCP server, including servers on the same auth table.
- Servers without sign-in are unchanged. The Xano MCP Server isn’t affected either.
- Clients need MCP authorization
2025-06-18or later. These follow the server’sresource_metadatapointer. Clients that only look for/.well-known/oauth-authorization-serverat the root of the host (the2025-03-26discovery) aren’t supported.
Turn on sign-in
- Builder
- XanoScript
1
Open the server’s settings
Open your MCP server and click Edit MCP Server. Under MCP Server Settings, find Sign-in (OAuth) and click Configure.2
Pick a mode and an auth table
Set Sign-in to Hosted or External, and pick the Auth table whose rows are the users who sign in. Only tables with authentication enabled are listed.3
Fill in the mode’s settings
For Hosted, enter your Login URL. See Hosted sign-in.For External, pick your Provider and enter the Issuer, Column and, if your provider needs it, Audience or Allowed client IDs. See External identity provider.4
Save and confirm
Click Save and confirm Turn on sign-in. The panel then lists the Endpoints to register for each of the server’s hosts.Settings
login_url, issuer and audience accept ${env.NAME} references to your environment
variables, so one release works in
every environment. If a referenced variable doesn’t exist, the server answers every request
with 503 “This MCP server’s sign-in is not configured correctly.” until you add it.
Hosted sign-in
In hosted mode, Xano is the authorization server and your login page does the signing in. Xano never renders a page your users act on, so your page also acts as the consent screen: it shows which client is asking for access and lets the user approve or deny it. You need:- An
httpspage you control, set as the Login URL. - Two endpoints in your workspace, authenticated on the server’s auth table, that call
mcp.oauth.requestandmcp.oauth.complete.
How the flow works
- The client registers itself and sends the user’s browser to the server’s authorize URL.
- Xano checks the client, its callback URL and PKCE, then redirects the browser to your
Login URL with
?mcp_request=<id>added. - Your page signs the user in, shows what’s asking for access, and asks them to approve or deny it.
- Your page calls your own endpoint, which runs
mcp.oauth.completewith the user’s decision and returns a continue URL. - Your page navigates the browser to the continue URL.
- Xano sends the browser back to the client, with an authorization code if the user
approved, or
access_deniedif they denied. - The client exchanges the code for an access token and a refresh token, and connects.
Build the login handoff endpoints
mcp.oauth.request reads what’s asking for access. mcp.oauth.complete records the user’s
answer and returns the continue URL. Both run in an endpoint that requires authentication on
the server’s auth table, in the same workspace as the MCP server.
mcp.oauth.request returns:
Build the login page
Your page readsmcp_request from its query string, signs the user in, asks them, and hands
the answer back. Keep mcp_request through your own redirects, such as social login or MFA.
It expires 10 minutes after the client started the flow.
/authorize replaced by /cancel, plus the same
mcp_request. The client gets access_denied. A user who’s already signed in and declines
should go through mcp.oauth.complete with decision = "deny" instead.
Refusals from mcp.oauth.request and mcp.oauth.complete
Refusals from mcp.oauth.request and mcp.oauth.complete
Both functions refuse with HTTP
400 and one of these messages, also returned as
reason in the error payload. Catch them with try_catch and branch on
$error.message.unsafe_user_id: signing in a user without $auth
unsafe_user_id: signing in a user without $auth
mcp.oauth.complete accepts unsafe_user_id to name the row directly. It skips both
the $auth check and the 10-minute session check, so anyone who can reach an endpoint
that passes it can sign in as that user.Tenants and datasources
Tenants and datasources
A server inside a tenant is signed in to through that tenant, so the endpoint that calls
mcp.oauth.complete must run in the same tenant. Otherwise it gets wrong_tenant.The datasource is recorded when the client starts the flow: from an x-data-source
query parameter on the authorize URL, or live otherwise. The endpoint that calls
mcp.oauth.complete, and every MCP request made with the resulting token, must use the
same datasource.Clients, tokens and sessions
- Client registration: clients can use Dynamic Client Registration (public clients
only, 1 to 10 redirect URIs) or a Client ID Metadata Document (an
httpsclient_id). A dynamic registration belongs to one server and one branch, and expires after 30 days unused. - Callbacks:
httpsURLs, loopback addresses on any port, and native-app schemes. - Grant type: authorization code with PKCE
S256only. No scopes are advertised. - Access tokens last 1 hour. Refresh tokens rotate on every use. Reusing one that was already rotated away revokes the whole sign-in, except for a single replay within 60 seconds, for a client that lost a response.
- A sign-in ends after 30 days without a refresh, or 90 days after it started. Each completed sign-in is its own grant, so a user signed in from two installations of the same client has two grants.
External identity provider
In external mode, your identity provider signs users in and issues the tokens. Xano checks each access token on every request and matches it to exactly one row of your auth table.
There’s no JWKS field: Xano reads
<issuer>/.well-known/openid-configuration, requires its
issuer to match yours exactly, and fetches the keys from its jwks_uri.
Provider setup
After you save, Endpoints to register lists, for each of the server’s hosts, the Resource (the connection URL, which is also the audience to register) and the Protected resource metadata URL. With Audience left empty, your provider must put the URL the client connected to intoaud.
These notes are based on each provider’s documented behavior. Check them against your
provider’s current docs; Test a token tells you which check a real token
fails.
Test a token
In the Sign-in (OAuth) panel, paste an access token from your provider under Test a token and click Test token. Xano checks it against the saved settings and shows whether it’s accepted. If it isn’t, you see the check that failed and why, and how many rows matched. The token isn’t stored.Every check a token goes through
Every check a token goes through
- It’s a signed JWT:
RS256–512,PS256–512,ES256/384/512orEdDSA.noneand HMAC are refused. - It’s signed by a key from your issuer’s key set, found by
kid. Keys are cached for 10 minutes. A newkidtriggers a refetch, at most once every 30 seconds. If your provider is unreachable, cached keys keep working. issequals your issuer exactly.expis in the future, andnbf/iataren’t, with 60 seconds of leeway.audcontains the URL the client connected to, or one of your Audience values.azp(orclient_id) is in Allowed client IDs, when set.- It’s an access token. Tokens carrying
nonceorat_hash(ID tokens) are refused. - Exactly one row has the claim’s value in Column, on the request’s datasource.
exp. Xano keeps no grants in external mode, so to cut a
user off immediately, change or remove their row so the claim no longer matches. Revoking at
your provider stops new tokens, not ones already issued.
Inside your tools
$authis the signed-in row, just as with a session token:$auth.idis the row ID, and a tool whose Authentication uses the same table passes.$auth.extrasis empty.- Every tool, prompt and resource on the server must use the sign-in auth table, or be public. The signed-in user is always a row of that table, so an item that requires a different table always fails. The builder doesn’t warn you about this.
- Don’t forward the access token.
$env.$request_auth_tokenand$http_headershold the MCP access token the client presented. Your regular endpoints refuse it. - The connection trigger receives
$input.auth, with the signed-in row’s{dbo, id, extras}. Use it to narrow the tools, prompts and resources each user sees. - AI agents don’t run as the user. Tools called by an agent from
ai.agent.runinside a tool don’t carry the signed-in user, so agent tools that require authentication fail with “Invalid token.” security.create_auth_tokeninside a tool still mints a normal session token.
Revoking access
What signs everyone out
Every session is bound to the server’s mode, auth table, claim, column, issuer, audience, provider and allowed client IDs. Changing any of them ends every session on the next request, and clients sign in again. That includes a change in the value of an environment variable that Issuer or Audience refers to. Changing the Login URL doesn’t sign anyone out.Branches, hosts and deploys
- Each branch signs in separately. A non-live branch (
67Dx5RNL:dev) has its own issuer, its own registered clients and its own tokens. Its tokens don’t work on the live branch. - Sign-in works only on the server’s own hosts: your instance host, its custom domains,
the workspace’s generated host and, inside a tenant, the tenant’s domain. Anywhere else,
the server answers
401“This MCP server cannot be signed in to at this address.” - The
oauthsettings travel withxano workspace push, releases, imports and branch clones. Grants and registered clients don’t, so users sign in again after a clone or import.
Discovery endpoints
You don’t need these to connect a client, which finds them on its own. They’re useful when you register the server with your identity provider or debug a client. The Endpoints to register list in the builder shows the exact URLs for each host.
The Metadata API exposes the same operations as the builder panel:
Limits
Rate limits and known limitations
Rate limits and known limitations
Rate limits
- Authorize: 60 requests per minute per IP, 600 per minute per server, and at most 500 sign-ins in progress per server.
- Token: 120 per minute per IP, 1,200 per server. Revoke: 60 per IP, 600 per server.
- Registration: 100 per hour per IP, 1,000 per server. Client ID Metadata Document fetches: 30 per minute per IP, 300 per server.
- Token tests: 20 per minute per server.
- Scopes aren’t mapped to tools. A signed-in user reaches every tool their Authentication settings allow.
- External mode has no automatic sign-up.
- Agent tools called from
ai.agent.runinside a tool don’t run as the signed-in user. - A self-hosted or local instance with no ingress host configured can’t start sign-in.
Troubleshooting
Next steps
Connecting Clients
Connect Claude, Cursor, VS Code and more to your MCP server.
Bring your own identity provider
Run your own OAuth front end with a Cloudflare Worker, for setups native sign-in doesn’t cover.