apps/docs/content/guides/auth/sessions.mdx
Supabase Auth provides fine-grained control over your user's sessions.
Some security sensitive applications, or those that need to be SOC 2, HIPAA, PCI-DSS or ISO27000 compliant will require some sort of additional session controls to enforce timeouts or provide additional security guarantees. Supabase Auth makes it easy to build compliant applications.
A session is created when a user signs in. By default, it lasts indefinitely and a user can have an unlimited number of active sessions on as many devices.
A session is represented by the Supabase Auth access token in the form of a JWT, and a refresh token which is a unique string.
Access tokens are designed to be short lived, usually between 5 minutes and 1 hour while refresh tokens never expire but can only be used once. You can exchange a refresh token only once to get a new access and refresh token pair.
This process is called refreshing the session.
A session terminates, depending on configuration, when:
Every access token contains a session_id claim, a UUID, uniquely identifying the session of the user. You can correlate this ID with the primary key of the auth.sessions table.
A session is initiated when a user signs in. The session is stored in the auth.sessions table, and your app should receive the access and refresh tokens.
There are two flows for initiating a session and receiving the tokens:
This feature is only available on Pro Plans and up.
</Admonition>Supabase Auth can be configured to limit the lifetime of a user's session. By default, all sessions are active until the user signs out or performs some other action that terminates a session.
In some applications, it's useful or required for security to ensure that users authenticate often, or that sessions are not left active on devices for too long.
There are three ways to limit the lifetime of a session:
To make sure that users are required to re-authenticate periodically, you can set a positive value for the Time-box user sessions option in the Auth settings for your project.
To make sure that sessions expire after a period of inactivity, you can set a positive duration for the Inactivity timeout option in the Auth settings.
You can also enforce only one active session per user per device or browser. When this is enabled, the session from the most recent sign in will remain active, while the rest are terminated. Enable this via the Single session per user option in the Auth settings.
Sessions are not proactively destroyed when you change these settings, but rather the check is enforced whenever a session is refreshed next. This can confuse developers because the actual duration of a session is the configured timeout plus the JWT expiration time. For single session per user, the effect will only be noticed at intervals of the JWT expiration time. Make sure you adjust this setting depending on your needs. We do not recommend going below 5 minutes for the JWT expiration time.
Otherwise sessions are progressively deleted from the database 24 hours after they expire, which prevents you from causing a high load on your project by accident and allows you some freedom to undo changes without adversely affecting all users.
Most applications should use the default expiration time of 1 hour. This can be customized in your project's settings in the JWT Keys > Legacy JWT Secret section.
Setting a value over 1 hour is generally discouraged for security reasons, but it may make sense in certain situations.
Values below 5 minutes, and especially below 2 minutes, should not be used in most situations because:
As your users continue using your app, refresh tokens are being constantly exchanged for new access tokens.
The general rule is that a refresh token can only be used once. However, strictly enforcing this can cause certain issues to arise. There are two exceptions to this design to prevent the early and unexpected termination of user's sessions:
Should the reuse attempt not fall under these two exceptions, the whole session is regarded as terminated and all refresh tokens belonging to it are marked as revoked. You can disable this behavior in the Advanced Settings of the Auth settings page, though it is generally not recommended.
The purpose of this mechanism is to guard against potential security issues where a refresh token could have been stolen from the user, for example by exposing it accidentally in logs that leak (like logging cookies, request bodies or URL params) or via vulnerable third-party servers. It does not guard against the case where a user's session is stolen from their device.
Traditionally user sessions were implemented by using a unique string stored in cookies that identified the authorization that the user had on a specific browser. Applications would use this unique string to constantly fetch the attached user information on every API call.
This approach has some tradeoffs compared to using a JWT-based approach:
Supabase Auth prefers a JWT-based approach using access and refresh tokens because session information is encoded within the short-lived access token, enabling transfer across APIs and systems without dependence on a central server's availability or performance. This approach enhances an application's tolerance to transient failures or performance issues. Furthermore, proactively refreshing the access token allows the application to function reliably even during significant outages.
It's better for cost optimization and scaling as well, as the authentication system's servers and database only handle traffic for this use case.
Most applications rarely need such strong guarantees. Consider adjusting the JWT expiry time to an acceptable value. If this is still necessary, you should try to use this validation logic only for the most sensitive actions within your application.
When a user signs out, the sessions affected by the logout are removed from the database entirely. You can check that the session_id claim in the JWT corresponds to a row in the auth.sessions table. If such a row does not exist, it means that the user has logged out.
Note that sessions are not proactively terminated when their maximum lifetime (time-box) or inactivity timeout are reached. These sessions are cleaned up progressively 24 hours after reaching that status. This allows you to tweak the values or roll back changes without causing unintended user friction.
This is possible, but only for apps that use the traditional server-only web app approach where all of the application logic is implemented on the server and it returns rendered HTML only.
If your app uses any client side JavaScript to build a rich user experience, using HTTP-Only cookies is not feasible since only your server will be able to read and refresh the session of the user. The browser will not have access to the access and refresh tokens.
Because of this, the Supabase JavaScript libraries provide only limited support. You can override the storage option when creating the Supabase client on the server to store the values in cookies or your preferred storage choice, for example:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('SUPABASE_URL', 'SUPABASE_PUBLISHABLE_KEY', {
auth: {
storage: {
getItem: () => {
return Promise.resolve('FETCHED_COOKIE')
},
setItem: () => {},
removeItem: () => {},
},
},
})
The customStorageObject should implement the getItem, setItem, and removeItem methods from the Storage interface. Async versions of these methods are also supported.
When using cookies to store access and refresh tokens, make sure that the Expires or Max-Age attributes of the cookies is set to a timestamp very far into the future. Browsers will clear the cookies, but the session will remain active in Supabase Auth. Therefore it's best to let Supabase Auth control the validity of these tokens and instruct the browser to always store the cookies indefinitely.