mirror of
https://github.com/supabase/supabase.git
synced 2026-06-25 12:37:15 +08:00
* docs: add server-side rendering guide for auth * docs: apply suggestions from code review Co-authored-by: dng <danny@supabase.io> Co-authored-by: dng <danny@supabase.io>
203 lines
8.5 KiB
Plaintext
203 lines
8.5 KiB
Plaintext
---
|
|
id: server-side-rendering
|
|
title: Server-Side Rendering
|
|
description: Render pages with user information on the server.
|
|
---
|
|
|
|
Single-page apps with server-side rendering (SSR) is a popular way to optimize rendering
|
|
performance and leverage advanced caching strategies.
|
|
|
|
Supabase Auth supports server-side rendering when you need access to user
|
|
information, or your server needs to authorize API requests on behalf of your
|
|
user to render content.
|
|
|
|
When a user authenticates with Supabase Auth, two pieces of information are
|
|
issued by the server:
|
|
|
|
1. **Access token** in the form of a JWT.
|
|
2. **Refresh token** which is a randomly generated string.
|
|
|
|
Most Supabase projects have their auth server listening on
|
|
`<project-ref>.supabase.co/auth/v1`, thus the access token and refresh token are
|
|
set as `sb-access-token` and `sb-refresh-token` cookies on the
|
|
`<project-ref>.supabase.co` domain.
|
|
|
|
:::note
|
|
These cookie names are for internal Supabase use only and may change without
|
|
warning. They are included in this guide for illustration purposes only.
|
|
:::
|
|
|
|
Web browsers limit access to cookies across domains, consistent with the
|
|
[Same-Origin Policy
|
|
(SOP)](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy).
|
|
|
|
Your web application cannot access these cookies,
|
|
nor will these cookies be sent to your application's server.
|
|
|
|
## Understanding the authentication flow
|
|
|
|
When you call one of the `signIn` methods, the client library running in the
|
|
browser sends the request to the Supabase Auth server. The Auth server determines
|
|
whether to verify a phone number, email and password combination, a Magic Link,
|
|
or use a social login (if you have any setup in your project).
|
|
|
|
Upon successful verification of the identity of the user, the Supabase Auth
|
|
server redirects the user back to your single-page app.
|
|
|
|
:::tip
|
|
You can configure [redirects URLs](https://app.supabase.com/project/_/auth/settings) in the Supabase Dashboard. You can use wildcard match patterns
|
|
like `*` and `**` to allow redirects to different forms of URLs.
|
|
:::
|
|
|
|
These redirect URLs have the following structure:
|
|
|
|
```
|
|
https://yourapp.com/...#access_token=<...>&refresh_token=<...>&...
|
|
```
|
|
|
|
The first access and refresh tokens after a successful verification are
|
|
contained in the URL fragment (anything after the `#` sign) of the redirect
|
|
location. This is intentional and not configurable.
|
|
|
|
The client libraries are designed to listen for this type of URL, extract
|
|
the access token, refresh token and some extra information from it, and finally
|
|
persist it in local storage for further use by the library and your app.
|
|
|
|
:::info
|
|
Web browsers do not send the URL fragment to the server they're making the
|
|
request to. Since you may not be hosting the single-page app on a server under
|
|
your direct control (such as on GitHub Pages or other freemium hosting
|
|
providers), we want to prevent hosting services from getting access to your
|
|
user's authorization credentials by default. Even if the server is under your
|
|
direct control, `GET` requests and their full URLs are often logged. This
|
|
approach also avoids leaking credentials in request or access logs.
|
|
:::
|
|
|
|
## Bringing it together
|
|
|
|
As seen from the authentication flow, the initial request after successful
|
|
login made by the browser to your app's server after user login **does not
|
|
contain any information about the user**. This is because first the client-side
|
|
JavaScript library must run before it makes the access and refresh token
|
|
available to your server.
|
|
|
|
It is very important to make sure that the redirect route right after login
|
|
works without any server-side rendering. Other routes requiring authorization
|
|
do not have the same limitation, provided you send the access and refresh
|
|
tokens to your server.
|
|
|
|
This is traditionally done by setting cookies. Here's an example you
|
|
can add to the root of your application:
|
|
|
|
```typescript
|
|
supabase.auth.onAuthStateChange((event, session) => {
|
|
if (event === "SIGNED_OUT" || event === "USER_DELETED") {
|
|
// delete cookies on sign out
|
|
const expires = new Date(0).toUTCString();
|
|
document.cookie = `my-access-token=; path=/; expires=${expires}; SameSite=Lax; secure`;
|
|
document.cookie = `my-refresh-token=; path=/; expires=${expires}; SameSite=Lax; secure`;
|
|
} else if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED") {
|
|
const maxAge = 100 * 365 * 24 * 60 * 60; // 100 years, never expires
|
|
document.cookie = `my-access-token=${session.access_token}; path=/; max-age=${maxAge}; SameSite=Lax; secure`;
|
|
document.cookie = `my-refresh-token=${session.refresh_token}; path=/; max-age=${maxAge}; SameSite=Lax; secure`;
|
|
}
|
|
})
|
|
```
|
|
|
|
This uses the standard
|
|
[`document.cookie` API](https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie)
|
|
to set cookies on all paths of your app's domain. All subsequent requests
|
|
made by the browser to your app's server include the `my-access-token` and
|
|
`my-refresh-token` cookies (the names of the cookies and additional
|
|
parameters can be changed).
|
|
|
|
In your server-side rendering code you can now access user and session
|
|
information:
|
|
|
|
|
|
```typescript
|
|
const refreshToken = req.cookies['my-refresh-token'];
|
|
const accessToken = req.cookies['my-access-token'];
|
|
|
|
if (refreshToken && accessToken) {
|
|
await supabase.auth.setSession({
|
|
refresh_token: refreshToken,
|
|
access_token: accessToken
|
|
});
|
|
} else {
|
|
// make sure you handle this case!
|
|
throw new Error("User is not authenticated.")
|
|
}
|
|
|
|
// returns user information
|
|
await supabase.auth.getUser();
|
|
```
|
|
|
|
Use `setSession({ access_token, refresh_token })` instead of
|
|
`setSession(refreshToken)` or `getUser(accessToken)` as refresh tokens or access tokens alone do not properly identify a user session.
|
|
|
|
Access tokens are valid only for a short amount of time.
|
|
|
|
Even though refresh tokens are long-lived, there is no guarantee that a user
|
|
has an active session. They may have logged out and your application failed to
|
|
remove the `my-refresh-token` cookie, or some other failure occurred that left
|
|
a stale refresh token in the browser. Furthermore, a refresh token can only be
|
|
used a few seconds after it was first used. Only use a refresh token if the
|
|
access token is about to expire, which will avoid the introduction of difficult
|
|
to diagnose logout bugs in your app.
|
|
|
|
A good practice is to handle unauthorized errors by deferring rendering the
|
|
page in the browser instead of in the server. Some user information is
|
|
contained in the access token though, so in certain cases, you may be able to
|
|
use this potentially stale information to render a page.
|
|
|
|
## Frequently Asked Questions
|
|
|
|
### How do I make the cookies `HttpOnly`?
|
|
|
|
This is not necessary. Both the access token and refresh token are designed to
|
|
be passed around to different components in your application. The browser-based
|
|
side of your application needs access to the refresh token to properly maintain
|
|
a browser session anyway.
|
|
|
|
### My server is getting invalid refresh token errors. What's going on?
|
|
|
|
It is likely that the refresh token sent from the browser to your server is
|
|
stale. Make sure the `onAuthStateChange` listener callback is free of bugs and
|
|
is registered relatively early in your application's lifetime.
|
|
|
|
When you receive this error on the server-side, try to defer
|
|
rendering to the browser where the client library can access an up-to-date
|
|
refresh token and present the user with a better experience.
|
|
|
|
### Should I set a shorter `Max-Age` parameter on the cookies?
|
|
|
|
The `Max-Age` or `Expires` cookie parameters only control whether the browser
|
|
sends the value to the server. Since a refresh token represents the
|
|
long-lived authentication session of the user on that browser, setting a short
|
|
`Max-Age` or `Expires` parameter on the cookies only results in a degraded
|
|
user experience.
|
|
|
|
The only way to ensure that a user has logged out or their session has ended
|
|
is to get the user's details with `getUser()`.
|
|
|
|
### What should I use for the `SameSite` property?
|
|
|
|
Make sure you [understand the behavior of the property in different
|
|
situations](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite)
|
|
as some properties can degrade the user experience.
|
|
|
|
A good default is to use `Lax` which sends cookies when users are
|
|
navigating to your site. Cookies typically require the `Secure` attribute,
|
|
which only sends them over HTTPS. However, this can be a problem when
|
|
developing on `localhost`.
|
|
|
|
### Can I use server-side rendering with a CDN or cache?
|
|
|
|
Yes, but you need to be careful to include at least the refresh token cookie
|
|
value in the cache key. Otherwise you may be accidentally serving pages with
|
|
data belonging to different users!
|
|
|
|
Also be sure you set proper cache control headers. We recommend invalidating
|
|
cache keys every hour or less.
|