Unable to authenticate with Supabase JWT in SpacetimeDB – OIDC public key fetch fails
Hi team,
I'm trying to authenticate a Supabase user with SpacetimeDB using their JWT (access_token) via the /v1/identity endpoint.
However, I'm encountering this error:
2025-04-13T13:43:47.145222Z DEBUG crates/core/src/auth/token_validation.rs:231: Getting validator for issuer https://{projectId}.supabase.co/auth/v1
2025-04-13T13:43:47.145787Z INFO crates/core/src/auth/token_validation.rs:209: Fetching key for issuer https://{projectId}.supabase.co/auth/v1
2025-04-13T13:43:47.767179Z WARN crates/core/src/auth/token_validation.rs:216: Error fetching public key for issuer https://{projectId}.supabase.co/auth/v1: FetchError(reqwest::Error { kind: Decode, source: Error("invalid type: integer `404`, expected struct OIDCConfig", line: 1, column: 3) })
It seems like SpacetimeDB is attempting to fetch .well-known/openid-configuration from Supabase to obtain the JWKS endpoint, but Supabase does not provide a standard OIDC config, resulting in a 404.
I do have the JWT secret (jwt_secret) from Supabase, and I was hoping to use that for verification instead. However, since I'm using Docker:
docker run --rm -p 3000:3000 clockworklabs/spacetime start
There doesn't seem to be a way to pass --jwt-secret or --jwt-public-key-url in this setup.
My original goal was to:
Authenticate via Supabase (access_token)
Pass the token to SpacetimeDB
Get back a valid { identity, token } pair
But I’m stuck due to this verification issue. Am I missing something fundamental here?
Questions: Does SpacetimeDB require a public JWKS URL (OIDC-compliant) for RS256 verification?
Can I provide the Supabase JWT secret instead for HS256 validation? If so, how can I configure it when using the start command or via Docker?
Is there a workaround for integrating Supabase JWT with SpacetimeDB?
Thanks in advance!
Hi @honeyhead could you provide the claims of the token? Or at least what the iss claim is? I will have to look into what Supabase does instead of the standard thing. Currently we do require a public JWKS URL (OIDC-compliant) for RS256 verification.
CC @jsdt
@cloutiertyler Thanks for the response. I looked into it as well, and it seems that Supabase doesn’t support a JWKS URL for security reasons. Since it uses PostgreSQL underneath, I’ll probably need to build a custom solution using an ASP.NET Web API.
This is a separate question, but when issuing identity and tokens through a REST API—if the authentication method works correctly with JWKS, should the identity remain consistent?
I haven't fully tested it with a proper JWKS-compatible setup yet, but what I'm seeing is that a new identity and token are generated each time, even though I'm sending the same token on every request.
Does that mean the authentication is failing, and that's why the identity isn't being preserved?
To answer your last question, yes the identity should remain consistent. I think you are hitting a bug in the typescript client that I need to fix - when auth fails, we are ignoring the error and creating a new identity, but we should be treating that as a connection error.
If you aren't using the typescript client, please let me know.
@jsdt Hi, I'm a .NET developer. I tested this using C#, and at first I thought it was an issue on the C# side — but I found the same problem even when using the REST API. I tested it with Postman as well.
In the Docker logs, it mentioned an issue with the iss claim, but I experienced the same behavior with both the identity creation endpoint and the POST function to request a WebSocket token.
The problem was that an identity and token were created every time — even if I sent an empty string or just some random text instead of a proper token.
In my case, the logs in Docker indicated that the iss couldn’t be recognized or found in the URL, but regardless, a new identity and token were generated every time.
While this might seem like it’s just creating a new access token, I believe the identity should remain the same even if the token changes. Since the authentication was effectively bypassed, I couldn't implement any internal login logic, and it was impossible to identify the user properly.
That’s why I was wondering — if I build a custom implementation where the iss is recognized correctly, will the identity and token then be generated properly and consistently?
@jsdt https://spacetimedb.com/docs/http/identity
I thought it might be a problem with the C# client, so I used the REST API instead, based on the URL provided.
Actually, since SpacetimeDB doesn’t validate the token itself but simply checks if it matches via JWKS, I believe it's totally fine to place a token issuing and verification server in the middle, and only forward verified tokens to SpacetimeDB.
However, even if I run a custom JWKS server, at this point it’s hard to know whether—when the JWKS is set up properly—SpacetimeDB will consistently return the same identity and token when a valid JWT is provided (or at least keep the identity stable). That uncertainty makes it difficult to proceed with building out the system.
At one point (I think when sending messages or maybe during connection), I saw logs saying headers were missing, although things sometimes worked fine. So it seems like header presence might also be affecting the behavior.
While I understand SpacetimeDB has been around for over two years now, honestly, the tutorials and documentation don’t feel very beginner-friendly. Ironically, the 90% discount on the cloud offering is appealing, but also makes me hesitate because it feels risky.
I really like this — it has a lot of potential — which is why it feels all the more disappointing.
@honeyhead as far as I can tell, the Identity is derived from subject and issuer claims on the token, so if they remain the same (and the issuer is validated through the keys listed in their OIDC configuration), you should get the same identity.
References:
- https://github.com/clockworklabs/SpacetimeDB/blob/bf395a66a69464a9176050a3dd5a33048819b57f/crates/lib/src/identity.rs#L112
- https://github.com/clockworklabs/SpacetimeDB/blob/bf395a66a69464a9176050a3dd5a33048819b57f/crates/client-api/src/auth.rs#L106-L109
To confirm what @AndreasHassing said, the Identity is based on the subject and issuer of the token, so if you authorize with the same token, you should be getting the same identity.
With the rest API that you linked to, the /v1/identity endpoint exists to create a new identity - it doesn't even look for credentials in the request. If you are getting an unexpected response with the /v1/identity/websocket-token endpoint (which just gives you a token with a shorter expiry), please let me know (ideally with a request that I can run to repro). I just tested that locally with an invalid token, and the request fails.
Could you clarify what issue you are having with the C# client? Is the problem that the client can't connect with a supabase token, or is the client able to connect, but being given new credentials?
Regarding supabase, I looked into their auth story, and the reason they don't expose public keys that can be used to verify tokens is that they use symmetric encryption (the same key is used for signing and verifying). They are working on switching to asymmetric encryption (you can track it here), but it's not an option yet. Once they support it, we can make sure supabase is usable as an auth provider.
hi-! @jsdt
In my .NET MAUI app, I used Supabase for user authentication and issued individual JWT tokens upon login. However, since Supabase does not support asymmetric encryption, I built a separate ASP.NET Web API to bridge the gap for SpacetimeDB integration.
This API verifies the Supabase JWT, then issues a new JWT signed with an asymmetric key pair. It also serves the required endpoints:
/.well-known/openid-configuration
/.well-known/jwks.json
These allow SpacetimeDB to verify the token using the public key, enabling seamless communication between Supabase and SpacetimeDB while preserving user identity.
I confirmed that using Supabase's /v1/identity/websocket-token endpoint successfully returns a consistent hex_identity. SpacetimeDB accepts this token without issues, and everything works smoothly thanks to the .well-known endpoint configuration.
Previously, in versions before 1.1.0, there was a problem where even an invalid JWT sent to /v1/identity/websocket-token would still result in a token being issued. Although Docker logs showed warnings such as an invalid iss or malformed token, a token would still be returned, which was a security concern. This was the main reason I started investigating the behavior of /v1/identity vs /v1/identity/websocket-token.
As far as I know, this issue has been fixed as of version 1.1.0.
On the other hand, I couldn’t use the /v1/identity endpoint because it generates a new identity and token every time without accepting external parameters. Since I wanted to preserve the identity based on the Supabase token, /v1/identity/websocket-token was the only viable option, and it is now working as expected.
Here is an example of a returned token:
{
"hex_identity": "c2009826ebeb1b22d94d95ff753ecb1d595bc143810aa9efed0b76f3c48aa22a",
"sub": "{my supabase uuid}",
"iss": "{my api iss url}",
"aud": [],
"iat": 1745028867,
"exp": 1745028927
}
I’d like to confirm whether this is considered a secure and recommended approach.
Sorry for the slow reply here @honeyhead
The approach you are using is a good way to work with symmetric-key JWTs.
That said, it looks like supabase has launched asymmetric keys, which should work out of the box (though I haven't tested them yet). I'd recommend migrating to those, so that you won't need the separate ASP.NET Web API.