-
-
Notifications
You must be signed in to change notification settings - Fork 6.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: add server-side rendering guide for auth
- Loading branch information
Showing
2 changed files
with
191 additions
and
0 deletions.
There are no files selected for viewing
190 changes: 190 additions & 0 deletions
190
apps/reference/docs/guides/auth/server-side-rendering.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
--- | ||
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 an opaque string. | ||
|
||
Most Supabase projects have their auth server listening on | ||
`<project>.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>.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 redirect to a login provider (for example, a social login), to | ||
verify a phone number, email and password combination, a magic link, etc. | ||
|
||
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 fragment (anything after the `#` sign) of the | ||
redirect URL. This is intentional and not configurable. | ||
|
||
The client libraries are designed to listen for this type of URL, extract | ||
the relevant information from it, and 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 total control, we want to prevent hosting services from getting access to | ||
your user's authorization credentials by default. Even if the server is under | ||
your total 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 first request made by the browser to | ||
your app's server after user login **does not contain any information about the | ||
user**. | ||
|
||
You need to make sure that the redirect route works without any server-side | ||
rendering. Other routes requiring authorization do not have the same | ||
limitation, provided you give your server the access and refresh tokens. | ||
|
||
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']; | ||
|
||
if (refreshToken) { | ||
supabase.auth.setSession(refreshToken); | ||
} else { | ||
// make sure you handle this case! | ||
throw new Error("User is not authenticated.") | ||
} | ||
|
||
// returns user information | ||
await supabase.auth.getUser(); | ||
``` | ||
|
||
Use `setSession(refreshToken)` instead of `getUser(accessToken)` as refresh | ||
tokens are long-lived credentials that provide user information even if the user | ||
has not used your app in a long time. | ||
|
||
Even though refresh tokens are long-lived, they 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. | ||
|
||
A good practice is to handle unauthorized errors by deferring to render 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 parameter in different | ||
situations](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite), | ||
as your users can be experiencing a degraded user experience. | ||
|
||
A good default is to use `Lax` which will send cookies when users are | ||
navigating to your site. Cookies typically require the `Secure` attribute, | ||
which will only send them over HTTPS. However, this can sometimes 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters