Softotic
Back to insights
23 August 202622 min readSupabaseOAuth 2.1SaaSAuthenticationMCP

Supabase OAuth 2.1 Server: When Should Your SaaS Become an Identity Provider?

Supabase Auth can now act as an OAuth 2.1 and OpenID Connect provider. Learn when a SaaS product should expose Sign in with Your App, MCP authentication, partner integrations, and client-aware RLS.

Supabase OAuth 2.1 Server: When Should Your SaaS Become an Identity Provider?

Supabase Auth can do something materially different from ordinary “Sign in with Google” authentication: your Supabase-backed product can itself become an OAuth 2.1 and OpenID Connect identity provider for other applications.

That is useful when a SaaS platform wants third-party apps, partner integrations, mobile clients, enterprise tools, or MCP/AI agents to act on behalf of its existing users. It is unnecessary when all you need is login for your own web application.

The most important implementation detail is easy to miss: OAuth scopes such as __INLINE_CODE_0__, __INLINE_CODE_1__, __INLINE_CODE_2__, and __INLINE_CODE_3__ do not control access to your Postgres data. Supabase says database/API authorization still belongs in Row Level Security, where OAuth tokens can be restricted using their __INLINE_CODE_4__ claim.

So the real architecture is:

text
OAuth establishes who the user authorised
        ↓
Supabase issues the token
        ↓
RLS decides what that client may actually access

This guide explains when that architecture is worth adopting and how to avoid turning “Sign in with our app” into “every integration gets the user's full normal permissions.”

What does Supabase OAuth 2.1 Server actually do?

Supabase Auth can now act as an OAuth 2.1 authorization server and OpenID Connect provider.

According to the current Supabase OAuth 2.1 Server documentation, that lets another application authenticate through your Supabase project in the same broad pattern users recognise from “Sign in with Google” or “Sign in with GitHub”.

Supabase lists use cases including:

  • developer platforms and marketplaces
  • third-party partner integrations
  • “Sign in with Your App”
  • AI agents and Model Context Protocol clients
  • first-party mobile and desktop clients
  • enterprise OpenID Connect integrations

The flow is no longer:

text
Your web app
   ↓
Supabase Auth
   ↓
Your database

It can become:

text
Third-party app
      ↓
Your Supabase OAuth authorization server
      ↓
Your login + consent UI
      ↓
Supabase access / refresh token
      ↓
Your API / Supabase data

That turns authentication into part of your product platform.

When should a SaaS product become an identity provider?

A SaaS product should consider becoming an OAuth/OIDC provider when other software needs delegated access to user-owned capabilities or data.

That usually appears in one of four product stages.

1. You are opening a developer ecosystem

Suppose customers use a project-management SaaS and want external tools to integrate with it.

Without delegated OAuth, integrations often begin badly:

text
User creates long-lived API key
      ↓
Pastes key into third-party service
      ↓
Third party effectively receives broad account access

A better model is:

text
Third-party integration
      ↓
"Connect to Your SaaS"
      ↓
User authenticates
      ↓
User sees consent
      ↓
Short-lived access token
      ↓
Access constrained by application policy

The OAuth server becomes useful when integrations are a supported product surface, not an internal workaround.

2. You want "Sign in with Your App"

If your platform owns a meaningful user identity that other applications should trust, OpenID Connect can let partners authenticate users using that identity.

Examples include:

  • a marketplace whose vendors use partner apps
  • an education platform whose users access connected learning tools
  • a property platform whose agents connect specialist services
  • a B2B platform with a partner ecosystem
  • a developer platform exposing user-authorised integrations

The product is effectively saying:

“This user has authenticated with our platform, and we can issue standards-based identity and access tokens.”

That is a larger responsibility than adding another social-login button to your own app.

3. AI agents or MCP clients need user-authorised access

Supabase explicitly positions OAuth 2.1 Server for Model Context Protocol authentication.

Its MCP authentication guide says an MCP server can use the existing Supabase user base so an AI client authenticates as the user who approved it, rather than receiving a shared service key.

The shape becomes:

text
AI / MCP client
      ↓
OAuth discovery
      ↓
Your Supabase Auth server
      ↓
User login + consent
      ↓
Access token
      ↓
MCP server
      ↓
User-authorised Supabase resources

This is much safer than giving an AI tool a project-wide secret.

It also creates an auditable question:

Which user authorised which OAuth client to operate on their behalf?

4. Your product needs standards-based federation

Supabase's OAuth server includes OpenID Connect support, including OIDC discovery, ID tokens with the __INLINE_CODE_0__ scope, and a UserInfo endpoint.

That can be useful where another system expects an OIDC identity provider rather than a custom JWT endpoint.

However, do not interpret “OIDC support exists” as “every enterprise SSO requirement is solved.”

Enterprise buyers may still require:

  • organisation-level tenant controls
  • domain discovery
  • SCIM provisioning
  • group/role mapping
  • audit logs
  • administrator consent
  • lifecycle/deprovisioning workflows
  • contractual/security controls

OAuth/OIDC is the protocol layer. Enterprise identity governance is a broader product.

When is Supabase OAuth Server unnecessary?

Do not enable an authorization server simply because OAuth sounds more “enterprise”.

You probably do not need it when:

  • only your own web app needs login
  • only your own Flutter/mobile app uses the same Supabase project and standard Supabase Auth already works
  • no third-party application needs delegated user access
  • integrations are server-to-server and do not act on behalf of end users
  • a simple internal service account is sufficient and appropriately secured
  • you are trying to fix ordinary login, session, or social-provider issues

A normal application already using:

text
Google / Apple / email login
        ↓
Supabase Auth
        ↓
RLS

does not benefit simply because Supabase can also become an OAuth provider.

The feature becomes valuable when your application is the identity or authorization boundary that other clients need to cross.

OAuth provider vs OAuth client: do not confuse the two

This causes a surprising number of design mistakes.

Your app as an OAuth client

The familiar pattern:

text
Your app
   ↓
Sign in with Google
   ↓
Google is OAuth/OIDC provider

Your app consumes identity from another provider.

Your Supabase project as an OAuth provider

The inverse:

text
Partner app
   ↓
Sign in with Your SaaS
   ↓
Your Supabase project is the OAuth/OIDC provider

Now your system issues tokens that another client relies on.

That means you own more of the security surface:

  • client registration
  • redirect URI configuration
  • consent experience
  • token claims
  • RLS authorization
  • revocation
  • third-party app governance

Turning on OAuth Server is therefore a platform decision, not just an authentication toggle.

How does the OAuth 2.1 flow work in Supabase?

Supabase currently supports:

  1. authorization code flow with PKCE
  2. refresh token flow

Its OAuth flow documentation says other grant types such as __INLINE_CODE_0__ and __INLINE_CODE_1__ are not supported.

A user-authorised flow looks like this:

text
Third-party client
       ↓
GET /oauth/authorize
       ↓
Supabase validates OAuth request
       ↓
Redirect to your authorization UI
       ↓
User authenticates
       ↓
Your UI shows client + requested scopes
       ↓
User approves or denies
       ↓
Authorization code
       ↓
Client exchanges code + PKCE verifier
       ↓
Access token + refresh token

PKCE matters because it protects the authorization-code exchange from interception.

Supabase recommends the authorization code flow with PKCE for public clients, mobile apps, single-page apps, and server-side applications.

You must build the consent experience

Supabase handles the OAuth protocol, but it does not make the product decision for you.

The current getting-started guide requires you to configure an authorization path and build the frontend that:

  1. receives the __INLINE_CODE_0__
  2. confirms whether the user is authenticated
  3. retrieves details about the requesting client
  4. shows the requested information/scopes
  5. lets the user approve or deny
  6. redirects using the URL returned by Supabase

A poor consent screen says:

text
Allow Example App?
[Allow]

A useful consent screen answers:

text
Who is asking?
What information is requested?
What will the integration be able to do?
Can the user deny it?
Can access be revoked later?

For AI-agent access, those questions become even more important because the end user may otherwise interpret the agent as having ambient authority over their account.

The biggest trap: OAuth scopes do not secure your database

This is the most important technical detail in the current Supabase implementation.

Supabase supports standard OAuth/OIDC scopes such as:

text
openid
email
profile
phone

But its Token Security and RLS documentation explicitly says:

OAuth scopes control identity information, not access to database tables or API endpoints.

So this request:

text
scope=openid email

does not mean:

text
this client can only access email-related database rows

The access token still represents an authenticated Supabase user.

Database authorization must be handled using Row Level Security and relevant token claims.

That is why the OAuth server should be designed together with RLS, not after it.

OAuth tokens include a __INLINE_CODE_0__ claim

Supabase OAuth access tokens include the OAuth application's __INLINE_CODE_0__.

Conceptually:

json
{
  "sub": "user-uuid",
  "role": "authenticated",
  "client_id": "partner-app-client-id"
}

That lets an RLS policy distinguish:

text
normal first-party user session

from:

text
same user acting through Partner App A

or:

text
same user acting through MCP Client B

This is the building block for client-aware authorization.

Why ordinary user RLS can be too broad for third-party clients

Imagine a normal first-party policy:

sql
create policy "Users can read their projects"
on public.projects
for select
to authenticated
using (
  owner_id = (select auth.uid())
);

That may be correct for your own trusted app.

But an OAuth token issued to a third-party client also represents the same authenticated user.

If you do nothing else, the third-party application may inherit the same row-level access that your first-party UI has.

That may be more authority than the user thought they were granting.

A better policy can inspect __INLINE_CODE_0__.

For example, conceptually:

sql
create policy "Partner client reads allowed projects"
on public.projects
for select
to authenticated
using (
  owner_id = (select auth.uid())
  and
  (auth.jwt() ->> 'client_id') = 'trusted-partner-client-id'
);

Real systems will usually need something richer than one hard-coded client.

The point is:

User identity answers whose data is involved. Client identity answers which application is acting on that user's behalf.

Both can matter.

Do not build a giant __INLINE_CODE_0__ policy

Hard-coding every integration into SQL can become difficult to operate.

A more scalable application might keep an internal registry:

text
oauth_clients
- client_id
- organisation_id
- status
- trust_level
- integration_type

and separate user grants:

text
oauth_client_grants
- user_id
- client_id
- permission_set
- granted_at
- revoked_at

Then authorization can reason about:

text
user
+
client
+
tenant
+
permission
+
resource

The exact schema depends on the product, but the architecture should allow integrations to be disabled, revoked, and audited without rewriting database policies for every partner.

Standard OAuth scopes are not yet custom application permissions

Supabase's current OAuth flow documentation says custom scopes are not currently supported.

Available scopes are centred on OIDC identity data:

  • __INLINE_CODE_0__
  • __INLINE_CODE_0__
  • __INLINE_CODE_0__
  • __INLINE_CODE_0__

So you cannot simply invent:

text
projects:read
projects:write
invoices:read

and expect Supabase's current scope system to enforce those database permissions.

Supabase says support for custom scopes is planned for a future release.

Until then, application-specific authorization belongs in your own authorization design, such as RLS, token claims, permission tables, or API-level checks.

This is another reason to avoid promising partners a scope model your platform cannot yet enforce natively.

Custom Access Token Hooks can add client-specific claims

Supabase's current getting-started documentation says Custom Access Token Hooks also run for OAuth token issuance.

That allows the token to be adapted using context such as the OAuth __INLINE_CODE_0__.

Possible uses include:

  • setting a meaningful __INLINE_CODE_0__ claim
  • adding client-specific metadata
  • adding internal permission-set identifiers
  • supporting RLS conditions

Conceptually:

text
OAuth authorization
       ↓
client_id known
       ↓
Custom Access Token Hook
       ↓
Add approved internal claims
       ↓
Issue JWT
       ↓
RLS / API validates claims

Do not put unbounded user-controlled data into JWTs.

Claims should describe trusted authorization facts, not become a portable database dump.

Use asymmetric signing keys for OAuth/OIDC

Supabase currently recommends asymmetric JWT signing algorithms such as RS256 or ES256 for OAuth use cases.

Why?

A third-party client can validate a token using your public key from the JWKS endpoint without receiving your signing secret.

The architecture becomes:

text
Supabase Auth
   ↓ signs token with private key
Access token
   ↓
Third-party/resource server
   ↓
Fetch public key from JWKS
   ↓
Verify signature

The current Supabase docs also say that if the __INLINE_CODE_0__ scope is requested, ID-token generation requires asymmetric signing. A project still using HS256 will fail to generate those OIDC ID tokens.

This should be part of the migration checklist before advertising “Sign in with Our App.”

Redirect URIs should be exact

Supabase's OAuth client configuration requires exact redirect URI matches.

Unlike some general Supabase authentication redirect settings, OAuth client redirect URIs do not support wildcard patterns.

That is a security feature.

A sloppy registration like:

text
https://partner.example/*

would create a larger redirect surface if it were allowed.

Prefer separate client registrations for:

text
development
staging
production

Supabase explicitly recommends separate OAuth clients per environment for stronger isolation and easier secret rotation/auditing.

Public vs confidential OAuth clients

Supabase supports both.

Public client

Examples:

  • mobile application
  • single-page application

A public client cannot safely store a client secret.

Its token endpoint authentication method is therefore __INLINE_CODE_0__, and PKCE protects the authorization flow.

Confidential client

Examples:

  • server-side web application
  • backend service with a secure secret store

A confidential client has a client secret and can authenticate to the token endpoint using supported client-secret methods.

Do not embed a confidential-client secret in:

  • Flutter
  • React Native
  • browser JavaScript
  • desktop binaries you cannot protect

If the secret ships to users, it is no longer a secret.

How does this work for MCP and AI agents?

The current MCP authorization specification uses OAuth-based discovery and bearer access tokens for protected servers.

The Model Context Protocol authorization specification requires MCP servers to expose protected-resource metadata and requires clients to discover an authorization server using supported OAuth/OIDC mechanisms.

Supabase's MCP guide supports this pattern through its OAuth server.

A clean architecture can look like:

text
MCP client
    ↓
Discovers OAuth metadata
    ↓
Supabase Auth
    ↓
User sees consent screen
    ↓
User approves AI tool
    ↓
OAuth token
    ↓
MCP server
    ↓
Business API / Supabase
    ↓
RLS checks user + client

The important boundary is:

The AI tool should receive delegated user authority, not your backend's master credential.

That preserves per-user authorization and makes revocation possible.

Dynamic client registration is useful, but expands your attack surface

Supabase supports dynamic client registration for MCP-compatible clients.

That reduces manual onboarding:

text
new MCP client
→ discovers auth server
→ registers itself
→ starts authorization flow

But “automatic registration” does not mean “automatic trust.”

Supabase's own MCP guidance recommends:

  • explicit user approval
  • displaying the client name and requested access
  • allowing denial
  • allowing later revocation
  • monitoring registered clients
  • validating redirect URIs

For a B2B SaaS with a small set of known integrations, manual client registration may be operationally safer.

For an open developer platform or broad MCP ecosystem, dynamic registration can reduce friction, but should be accompanied by stronger governance and monitoring.

MCP auth is not the same as the hosted Supabase MCP development server

This distinction matters.

Supabase has documentation for its hosted MCP tooling used by developers, and it separately documents how you can build your own MCP server that authenticates your end users through your Supabase OAuth server.

Those are different use cases.

For a production SaaS integration, the architecture is:

text
Your MCP server
+
Your OAuth server configuration
+
Your user consent
+
Your RLS / API rules

Do not assume that connecting Supabase's own developer MCP server to a project is the production pattern for exposing your customer's business data to arbitrary agents.

What about server-to-server integrations?

Supabase's current OAuth 2.1 server does not support the __INLINE_CODE_0__ grant.

That matters when an integration has no human user.

Example:

text
Nightly warehouse sync
→ Server A calls Server B
→ No end user is delegating access

That is not the same problem as:

text
User authorises Partner App to read their projects

If your requirement is machine-to-machine authentication, do not force a user-delegated authorization-code flow into it just because the product now has OAuth Server.

Evaluate a proper server credential, service account, signed request, or another suitable machine-auth pattern for the backend.

The authentication mechanism should match the actor.

A practical decision matrix

RequirementSupabase OAuth 2.1 Server fit
Login for your own Supabase web appUsually unnecessary
Third-party app acts on behalf of a userStrong fit
“Sign in with Your SaaS”Strong fit
MCP/AI client accesses user-authorised dataStrong fit
Partner marketplace integrationsStrong fit
OIDC identity federationPotentially strong fit
Machine-to-machine job with no userCurrent OAuth server flow may not be the right fit
Need custom OAuth scopes like __INLINE_CODE_0__ todayCurrent native scope model is limited
Need enterprise provisioning/SCIM lifecycleOAuth/OIDC alone is incomplete
Need a quick shared API key for one trusted internal scriptProbably over-engineered

An architecture for a SaaS integration platform

A production design can separate protocol, consent, authorization and domain access:

text
Third-party client / MCP client
          ↓
OAuth discovery + PKCE
          ↓
Supabase Auth
          ↓
Your consent UI
          ↓
OAuth token
          ↓
API / MCP resource server
          ↓
Token verification
          ↓
User + client + tenant authorization
          ↓
Supabase / application services
          ↓
Audit event

The layers have distinct responsibilities.

Supabase Auth

Handles:

  • OAuth authorization endpoints
  • authorization codes
  • PKCE
  • access/refresh tokens
  • OIDC discovery
  • JWKS

Your consent UI

Explains:

  • which app is asking
  • which identity information is requested
  • what application-level access will be granted
  • how the user can deny or revoke

Your authorization layer

Decides:

  • which clients are trusted
  • which tenant the user belongs to
  • what data/actions the client may use
  • whether a permission has been revoked
  • whether sensitive operations need extra approval

RLS / API layer

Enforces the decision on real data.

Audit layer

Records useful events such as:

  • client registered
  • user consent granted
  • consent revoked
  • suspicious token/client behaviour
  • protected integration action

This makes OAuth a platform capability rather than a decorative login button.

How should multi-tenant SaaS handle OAuth clients?

A multi-tenant system adds another dimension.

A user can belong to:

text
Organisation A
Organisation B

and a third-party integration may be approved for:

text
Organisation A only

A token proving the user identity is not enough.

The resource check may need:

text
user_id
+
client_id
+
organisation_id
+
permission

For example, an API operation might require:

text
User belongs to organisation
AND
OAuth client is approved for organisation
AND
Grant includes required app permission
AND
Resource belongs to organisation

This is where OAuth and multi-tenant RLS meet.

If your current RLS only checks:

sql
auth.uid() = owner_id

you may need a more explicit integration authorization model before opening the platform to third-party clients.

For deeper RLS debugging, Softotic's existing guide on Supabase Storage 403 and row-level security covers the broader distinction between request identity, database grants and RLS policy evaluation.

What should you log?

Do not log raw access or refresh tokens.

Do log enough to investigate integration problems.

Useful OAuth audit fields can include:

text
event
user_id
client_id
organisation_id
authorization_id
grant/consent version
timestamp
result
reason
request correlation ID

For API actions, log the client identity separately from the user.

That allows you to answer:

“Did the user do this through our first-party app or through Partner App X?”

That distinction can be crucial during incident response.

What should you not do?

Do not treat OIDC scopes as database permissions

__INLINE_CODE_0__ and __INLINE_CODE_1__ control identity claims, not access to application tables.

Do not let every OAuth token inherit every first-party RLS permission by accident

Review policies for client-aware access.

Do not expose client secrets in mobile or browser apps

Use public clients with PKCE.

Do not keep HS256 if your OIDC implementation requires ID tokens

Supabase requires asymmetric signing for OIDC ID-token generation.

Do not allow broad redirect URI wildcards

Supabase OAuth clients require exact redirect URIs. Keep that discipline.

Do not enable dynamic registration without governance

Automatic registration is not automatic trust.

Do not give an MCP tool a Supabase secret key when user-delegated OAuth can solve the problem

A project-wide secret bypasses the user-level authorization model.

Do not promise custom OAuth scopes that the current native implementation does not support

Build application permissions separately until your chosen platform capability supports the model you need.

A secure rollout sequence

Phase 1: define the product use case

Write down:

  • who the client is
  • which user is delegating access
  • what the integration needs to do
  • whether the client is public/confidential
  • whether dynamic registration is needed
  • whether the system is multi-tenant

Phase 2: define application permissions

Do this before the consent screen.

Examples:

text
Read projects
Create tasks
Read invoices
Trigger deployment
Access support tickets

Even if these are not native OAuth scopes, the product needs an explicit permission model.

Phase 3: update authorization/RLS

Ensure the policy can distinguish:

text
user
client
tenant
permission

where appropriate.

Phase 4: configure OAuth/OIDC

  • enable OAuth 2.1 Server
  • configure authorization path
  • migrate to asymmetric signing if required
  • register test client
  • configure exact redirect URIs
  • build consent UI

Phase 5: test security failures

Test:

  • wrong redirect URI
  • missing/incorrect PKCE verifier
  • denied consent
  • revoked integration
  • wrong tenant
  • valid user but disallowed client
  • expired access token
  • refresh flow
  • public client with no secret
  • confidential client with wrong secret
  • MCP discovery failure
  • dynamic registration abuse controls

Phase 6: launch with auditability

Monitor:

  • new client registrations
  • consent grants/revocations
  • token errors
  • denied RLS requests
  • per-client API volume
  • unusual client behaviour
  • integration support failures

OAuth becomes operational infrastructure once partners depend on it.

FAQs

Can Supabase act as an OAuth 2.1 provider?

Yes. Supabase Auth can act as an OAuth 2.1 authorization server and OpenID Connect identity provider so third-party applications can authenticate users and receive access/refresh tokens.

Can I build “Sign in with My App” using Supabase?

Yes. Supabase documents “Sign in with Your App” as a core OAuth Server use case. You configure the OAuth server, register clients, and build your own authorization/consent UI while Supabase handles the protocol and token issuance.

Do OAuth scopes control Supabase database access?

No. Supabase's current documentation says the standard OAuth/OIDC scopes control identity data included in tokens/UserInfo, not access to database tables or APIs. Use Row Level Security and application authorization to control data access.

Does Supabase support custom OAuth scopes?

Not currently in the OAuth server documentation reviewed for this article. The current supported scopes are __INLINE_CODE_0__, __INLINE_CODE_1__, __INLINE_CODE_2__, and __INLINE_CODE_3__; Supabase says custom scopes are planned for a future release.

Can Supabase OAuth authenticate MCP clients?

Yes. Supabase documents MCP authentication as a first-class OAuth Server use case, including OAuth discovery, optional dynamic client registration, user consent, token exchange, and RLS-backed access.

Does Supabase OAuth Server support __INLINE_CODE_0__?

No. The current OAuth flow documentation supports authorization code with PKCE and refresh tokens, and says grant types such as __INLINE_CODE_0__ and __INLINE_CODE_1__ are not supported.

Conclusion

Supabase OAuth 2.1 Server is most useful when a Supabase-backed product stops being one application and starts becoming a platform.

The architecture decision is straightforward:

text
Only our own app needs login
→ ordinary Supabase Auth may be enough

Third-party app needs user-authorised access
→ OAuth Server becomes useful

MCP/AI tool needs user-authorised access
→ OAuth Server + consent + RLS

Partner needs machine-only access
→ evaluate a machine-auth pattern separately

The security decision is equally important:

text
OAuth authenticates the user and client
        ↓
RLS / application policy authorizes the data and actions

Do not confuse those layers.

A clean OAuth flow with poor RLS can still give an integration too much access.

A perfect RLS policy with a confusing consent screen can still create a bad trust model.

And a powerful MCP agent using a project-wide secret is not delegated authorization at all.

If your SaaS is becoming an integration platform, developer ecosystem, or AI/MCP-enabled product, Softotic's custom software development service can design the platform and authorization architecture, while AI and machine learning development can support agent and MCP workflows that need controlled user-authorised access.

Sources and references