Skip to content

Commit

Permalink
docs: add server-side rendering guide for auth
Browse files Browse the repository at this point in the history
  • Loading branch information
hf committed Sep 20, 2022
1 parent 691bd2d commit e433da5
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 0 deletions.
189 changes: 189 additions & 0 deletions apps/reference/docs/guides/auth/server-side-rendering.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
---
id: authz-ssr
title: Server-Side Rendering
description: Render pages with user information on the server.
---

Single page apps with server-side rendering (SSR) have exploded in popularity.
Typically you choose server-side rendering for your app 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 application running in the web browser is unable to access these cookies,
nor will those cookies be sent to requests 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. A decision is then made
whether to redirect to a login provider (for social login, for example) or 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 back to your single-page app.

:::tip
Redirects are limited by an allow list under Settings > Authentication >
Redirect URLs 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=<...>&...
```

As you can see the first access and refresh tokens after successful
verification are contained in the fragment (anything after the `#` sign) of the
redirect URL. This is intentional and not configurable.

Our client libraries are designed to listen for this type of URL and extract
the relevant information from it, after which they persist it in local storage
for further use by the library and your app.

:::info
Web browsers will not send the URL fragment to the server they're making the
request. 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 URL is often logged. This
approach avoids leaking credentials in request or access logs accidentally too.
:::

## Bringing it together

As seen from the authentication flow, the first request made by the browser to
your app's server after user login **will 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 a snippet of code you
should add at the root of your app's code:

```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`;
document.cookie = `my-refresh-token=; path=/; expires=${expires}; SameSite=Lax`;
} 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`;
document.cookie = `my-refresh-token=${session.refresh_token}; path=/; max-age=${maxAge}; SameSite=Lax`;
}
})
```

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 will include the `my-access-token` and
`my-refresh-token` cookies. (Do change the names of the cookies and additional
parameters as they suit you.)

In your server-side rendering code you can now access user and session
information in the following way:


```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.")
}

await supabase.auth.getUser(); // will return user information
```

We recommend using the `setSession(refreshToken)` method instead of
`getUser(accessToken)`, as refresh tokens are long lived credentials that will
give your server user information even if the user has not user your app in a
long while.

:::tip
Even though refresh tokens are long lived, they are 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 unpredicted failure
occurred that left a stale refresh token in the browser. **A good rule-of-thumb
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.

If possible, 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
will send 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 will only result in a degraded
user experience.

The only way to ensure that a user has logged out or their session has ended in
some way is to check with a call to `getUser()`.

### What should I use for the `SameSite` property?

Make sure you understand the parameter correctly, as your users can be
experiencing a degraded user experience.

A good default is to use `lax`.

### 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.
1 change: 1 addition & 0 deletions apps/reference/nav/_referenceSidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const sidebars = {
items: [
'guides/auth/row-level-security',
'guides/auth/managing-user-data',
'guides/auth/authz-ssr',
],
},
{
Expand Down

0 comments on commit e433da5

Please sign in to comment.