The sAccessToken wasn't set in the browser because it is too large. This will result in session verification to fail for API calls. The reason the access token can be large is because of too many custom claims, or because you added too many roles / permissions to the user. In this case, you can consider reducing the the number custom claims in the access token, or then switching to header based auth.
apiDomain set on the frontend doesn't match the actual API domain - so the interceptor is not called.
Access-Control-Expose-Headers not set properly - which prevents frontend from reading the front-token, which prevents setting that state on the frontend.
Access-Control-Expose-Headers set to * - which prevents frontend from reading the front-token, which prevents setting that state on the frontend. Even though it's star, it won't work cause of using credentials.
sameSite is none, but user set the cookie secure flag to false.
Access control allow headers not set properly, failing CORS
anti-csrf not being provided (either token if that is the mode, or custom header)
Issues with iframes
API and website domain do not share the same TLD (for example API is on api.example.com and website is not on *.example.com and not on example.com), so Safari will not send cookies even if sameSite is none. Switch to using header based auth.
Access-Control-Allow-Origin set to * which prevents browser from using setCredneitials: true. And if setCrednetials is false, then cookies are not sent.
Imported from different recipe by mistake.
Custom interceptor added which messes up refresh APIs. If using axios, they should add their interceptor AFTER both addAxiosInterceptor is called and after supertokens.init is called, this way, even if they are breaking the axios interface, it will still all work
Cookies have secure attribute in them, but the API domain being queried is HTTP
User's reverse proxy has a size limit on the response payload / header (can be configured on nginx). This in turn causes the access token from not being sent to the frontend, and the frontend getting a 502 (or another 5xx) error.
Session being created via an API that is not called by the frontend of the user's app. This can happen in case they are using some third party auth provider that calls their API webhook directly.
Using node-fetch instead of browser fetch even on client side. People might be using node-fetch for server side rendering and then not realising that they are using that for client as well. Our interceptors are added to window.fetch so their node-fetch wouldn't get the interceptors added, preventing a call to the refresh API.
Using sveltekit's fetch instead of window.fetch
Calling an APi from server side rendering instead of from the client.
Access control allow credentials may be false preventing cookies from being sent. This can happen if the frontend interception is not happening, or if the user has manually set that header to false.
Session.init not called, or being called after using axios / fetch (even though interceptor is being added)
websiteDomain is set to 127.0.0.1 but website is being loaded on the browser using localhost (or vice versa).
document.cookie doesn't work. This usually happens in case of electron apps or some other browser emulation tech. In this case, we see infinite refreshes being called. To solve this, we should ask the user to pass their own cookieHandler in the init function call
Have a proxy in the middle of frontend and backend which strips away necessary headers like cookies. This causes weird behaviour where refresh might return unauthorised and the frontend would clear frontend set cookies, but the backend sAccessToken and stuff still remains..
if set, check that the value of sessionScope is correctly set. If incorrectly set, it yields sFrontToken or st-last-access-token-update not being set in frontend cookies storage even after session refreshing returns 401 on first visit.
cookieDomain not correctly set which yiels access token and others not being attached to apiDomain
On the frontend, websiteBasePath is set to an incorrect value. This leads the auth page to navigate to some other page than what is expected
For domains like heroku, if the website domain and api domain are not equal or not a sub domain (disregard the .herokuapp.com common part), then same site has to be set to none. Note that this will not work on safari. Switch to using header based auth.
User is using another session management solution like django's built in which returns 401 causing refresh session to be called which returns 200, and then it goes in an infinite loop.
User is using auto generated client code (from swagger for example), which also seems to add a fetch interceptor. That fetch interceptor probably runs after ours, and if the user gives credentials: "same-origin", to the headers for that lib, the cookies won't be sent.
In react native the user may have either accidentally used CookieManager.clearAllCookies or they might be manually adding cookies while replacing existing ones.
The user has explicitly set access-control-expose-headers and their values are incorrect. Their headers override ours. This can cause an issue in which the access token and stuff gets set, but the sFrontToken on the frontend cookie don't get set.
The apiDomain and websiteDomain are on a shared domain (like azurewebsite.net) and the api and website are diffrent sub domains of this - but our SDK doesn't recognise that this is a shared domain and sets the cookie same site to lax, even though it should be none.
Changing of cookie domain value can lead to older session tokens (refresh token) still lingering around in the browser, and if that refresh token gets picked up during session refreshing, then refreshing will always fail - even for new logins. The solution is to clear all cookies in the browser.
If verifySession returns 403, and the response contains claimValidationErrors: id: st-ev, this means that the user needs to go through the email verification flow
Changing cookieDomain on the backend whilst a session exists may lead to having two sAccessTokens which will cause issues like the session not being verified correctly. The solution here is to manually clear the cookies and relogin.
In iOS, when you open webview in the app, and it has say, cookie like sAccessToken, and the react native app itself has header based auth, then when you make requests in the RN app to the backend, it adds the sAccessToken from the web UI cookie as well! So the request has both, the auth bearer token and the sAccessToken in it. Now, this works fine when there is an auth bearer token, as the backend SDK just uses that. However, if you log out in the RN app, and then when you try and login, the request will have just the sAccessToken. Now if that sAccessToken has expired, then the sign in API will send a 401, and the frontend wont even try and refresh cause it (correctly) thinks that the session doesn’t exist! The way to fix this would be to enforce using header based auth on the backend for mobile apps.
Dev stored stale access token in memory and is using that in requests instead of reading each time from storage.
apiBasePath does not match on the frontend and backend:
This can imply that the refresh cookie is set on a different path, breaking the refresh flow.
If the inital refresh API call (without a session) is returning a 200 instead of 404, it means that the user's backend is returning 200 for not found routes (which is strange), but it has happened before. This would then mean that the frontend prints the warning of front token is not being able to be set: Failed to retrieve local session state from cookies after a successful session refresh. This indicates a configuration error or that the browser is preventing cookie writes.
If the refresh API returns a 500 error -> this implies there are multiple session tokens (which happens cause of cookieDomain change), but the user has forgotten to set the olderCookieDomain config in backend session.init.
There was a case where the frontend was sending one sAccessToken, but the backend was getting 2 of them. This happened cause of some issue in golang, fiber framework which somehow caused cookies to be duplicated on the backend, and then our middleware saw two cookies instead of one.
If the user is seeing front-token change on the frontend during api request, (like the user id in it changing, or some claim changing) even though they have not made the change, the issue is usually due to caching. This can be solved by adding a header like res.setHeader('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate'); to your apis.
If using an API gateway, make sure to add rid,fdi-version,anti-csrf,st-auth-mode to the existing Access-Control-Allow-Headers in the API gateway settings
If you are seeing `Failed to retrieve local session state from cookies after a successful session refresh. This indicates one of the two issues:
a configuration error or that the browser is preventing cookie writes.on the frontend, then it is most likely due to a misconfig of thesessionTokenFrontendDomain` (if you have set it). Either remove that config, or set it to a value that has the same top level domain as the current browser url.
The backend is not sending the front-token response header in the refresh API response headers. So check why that would happen.
The backend is sending a front-token in the refresh API response header, but the Access-Control-Expose-Headers does not contain front-token
If you are using some extension like Jam, it MAY conflict with how we intercept the network request when calling fetch which prevents our network interceptors from running. This means that sFrontToken doesn't get saved in cookies when you login, which means that the user will be stuck on the login screen. Disabling such an interceptor should make the flow work.
App info related issues
If the user is seeing a tenant not found error in their api calls, its likely that the apiBasePath is misconfigured (not matching). This can happen if something like /api/auth is on the frontend, and /api is on the backend. This way, the auth part of the path is considered the tenantid, which probably wont exist.
If using a reverse proxy that strips away part of the path, then be sure to configure apiGatewayPath on the frontend and backend
Social / enterprise login related issues
The user provided their own scope but did not include the minimum, email, scope
The signinup API is called twice from the frontend in react strict mode + nextjs which results in one of the calls failing (only for development mode)
The user started the auth flow with one redirect uri but used a different one when calling /signinup. If both URLs are whitelisted on the Google config then google responds with {error: "invalid_grant", error_description: "Bad Request"}
For sign in with apple, if the user starts the flow (redirects the end user to apple's sign in page) with a redirect uri different than the one the SDK provides (apiDomain + basePath + /callback/apple) then the signinup flow will always fail. This is because the SDK always uses the built in URL as the redirect uri and when we query apple for the access token information, Apple will always complain about the redirect uri not matching (with a "invalid_grant" error)
Incorrect bundle id given for apple causes issues with parsing id token headers
The app uses server side rendering, and the consume code API being called is first happening on the server side and then on the client side. This makes the client side one fail with an "invalid_grant" error (at least for google)
For sign in with apple on React Native, if the user is building with Expo then in dev mode the default the client id sent to apple by expo is different that the apps bundle id (expo.client.io...). The user can build the iOS app with a development certificate (not expo dev mode but iOS dev build) to test apple sign in with SuperTokens
If the state in sessionStorage is different than the state in the url, it's usually cause the authorizationurl GET API was called more than once, but the user was redirected to the first url even though the second url's state was calculated and stored in the session storage
For apple login, if the user has put the same values for redirectURIOnProviderDashboard and frontendRedirectURI when calling getAuthorisationURLWithQueryParamsAndSetState, then it means that the state generated by the frontend won't have the frontendRedirectUri which will then make the apple callback on the backend fail with a 500 error.
Created state (by calling authorize) on one domain, lets say a.example.com overriding redirectURIOnProviderDashboard set to another domain b.example.com and post third party login, called signInUp from b.example.com. This fails because session storage of b.example.com doesn't contain the state information.
If a user is seeing 404 on the callback page, it's cause their frontend webserver is not serving their frontend app on that route. So this is not a supertokens related config issue, but is instead a webserver config related issue.
For google sign in, if you are getting invalid_grant error in the signinup API call, it could be because your system clock is not correctly set (most likely it's set to the future)
If you are getting an error like please provide exactly one client config or pass clientType or tenantId, when the signinup API is called, then it means that:
You have not provided any client id or secret to the provider, so provide at least one.
If you have provider more than one, then you need to always add the clientType setting on the frontend for all your frontends:
For web frontend, the clientType is a config in the supertokens.init on the frontend
For mobile, it goes as a request body prop when calling the signinup.
Frontend pre built UI related issues:
If the user is seeing a blank screen when they visit routes from the pre built UI paths, it could be cause they have not added the prebuiltui components to the function which renders then (to getSuperTokensRoutesForReactRouterDom or to getRoutingComponentand canHandleRoute)
Passwordless related issues:
If you are modifying the passwordless magic link url to add a query param for example, be mindful that the link has a fragment to it (https://..../auth/...?preAuthSessionId=...#<code>). The stuff after the # is called the fragment. So if you want to add a query param to this link, you need to do it before the # part of the link.
If you are seeing an error like "Unexpected end of JSON input" from our node sdk, it's mostly due to an issue with using bun runtime instead of node
Django specific issue
If you find that the supertokens middleware is not being called at all (you can detect this is the apis return a 401 and enabling debug logs don't generate any logs when the api is called), it's likely that something in Django is interfering. For example, DebugToolbar in Django can cause this issue.
General other issues:
If a user is seeing 404 when querying one of the apis exposed by the supertokens middleware, it's mostly due to incorrect appInfo config. It can be debugged by enabling backend sdk logs and seeing the output of that when making the api call
If the user is seeing a // in the request path to the api like <apiDomain/auth//... then it means they are returning an empty string from the getTenantId function from the frontend sdk. They should not return an empty string
If you are getting the error of supertokens not initialised, it could mean:
You are not calling the supertokens.init function in the global scope of your app. If you are using edge / lambda functions on the backend, make sure to call the supertokens.init on the backend in the global scope of each of the functions since they can be run in their own memory space
You are calling supertokens.init, but you have not init the correct set of recipes.
You have init the correct recipes, but you are importing from a wrong recipe.
You are using the frontend sdk functions on the backend (for example during SSR).
You are using the backend sdk functions on the frontend by mistake due to an import error.
We have frontend and backend debug logs that you can enable to help debug issues: https://supertokens.com/docs/thirdpartyemailpassword/troubleshooting/how-to-troubleshoot
Session related issues
Access-Control-Expose-Headers
not set properly - which prevents frontend from reading the front-token, which prevents setting that state on the frontend.Access-Control-Expose-Headers
set to*
- which prevents frontend from reading the front-token, which prevents setting that state on the frontend. Even though it's star, it won't work cause of using credentials.api.example.com
and website is not on*.example.com
and not onexample.com
), so Safari will not send cookies even if sameSite isnone
. Switch to using header based auth.node-fetch
instead of browser fetch even on client side. People might be using node-fetch for server side rendering and then not realising that they are using that for client as well. Our interceptors are added to window.fetch so their node-fetch wouldn't get the interceptors added, preventing a call to the refresh API.credentials: "same-origin",
to the headers for that lib, the cookies won't be sent.CookieManager.clearAllCookies
or they might be manually adding cookies while replacing existing ones.verifySession
returns 403, and the response containsclaimValidationErrors: id: st-ev
, this means that the user needs to go through the email verification flowFailed to retrieve local session state from cookies after a successful session refresh. This indicates a configuration error or that the browser is preventing cookie writes.
res.setHeader('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate');
to your apis.rid,fdi-version,anti-csrf,st-auth-mode
to the existingAccess-Control-Allow-Headers
in the API gateway settingsThe "listener" argument must be of type function. Received an instance of Object
, then see this thread: https://discord.com/channels/603466164219281420/1278371807925108791/1278371807925108791on the frontend, then it is most likely due to a misconfig of the
sessionTokenFrontendDomain` (if you have set it). Either remove that config, or set it to a value that has the same top level domain as the current browser url.Access-Control-Expose-Headers
does not containfront-token
fetch
which prevents our network interceptors from running. This means that sFrontToken doesn't get saved in cookies when you login, which means that the user will be stuck on the login screen. Disabling such an interceptor should make the flow work.App info related issues
/api/auth
is on the frontend, and/api
is on the backend. This way, theauth
part of the path is considered the tenantid, which probably wont exist.Social / enterprise login related issues
redirectURIOnProviderDashboard
andfrontendRedirectURI
when callinggetAuthorisationURLWithQueryParamsAndSetState
, then it means that the state generated by the frontend won't have the frontendRedirectUri which will then make the apple callback on the backend fail with a 500 error.a.example.com
overridingredirectURIOnProviderDashboard
set to another domainb.example.com
and post third party login, called signInUp fromb.example.com
. This fails because session storage ofb.example.com
doesn't contain the state information.invalid_grant
error in the signinup API call, it could be because your system clock is not correctly set (most likely it's set to the future)please provide exactly one client config or pass clientType or tenantId
, when the signinup API is called, then it means that:Frontend pre built UI related issues:
getSuperTokensRoutesForReactRouterDom
or togetRoutingComponent
andcanHandleRoute
)Passwordless related issues:
https://..../auth/...?preAuthSessionId=...#<code>
). The stuff after the#
is called the fragment. So if you want to add a query param to this link, you need to do it before the#
part of the link.AWS specific issues
Node SDK specific issue
bun
runtime instead ofnode
Django specific issue
DebugToolbar
in Django can cause this issue.General other issues:
//
in the request path to the api like<apiDomain/auth//...
then it means they are returning an empty string from thegetTenantId
function from the frontend sdk. They should not return an empty string