mirror of
https://github.com/supabase/supabase.git
synced 2026-07-05 02:24:20 +08:00
660 lines
22 KiB
Plaintext
660 lines
22 KiB
Plaintext
---
|
||
id: stytch
|
||
title: 'Stytch'
|
||
description: "Build a Next.js application powered by password-less authentication from Stytch, and Supabase's Row Level Security (RLS)."
|
||
---
|
||
|
||
In this guide we will build a simple expense tracker web application using Stytch, Supabase, and Next.js.
|
||
|
||
[Stytch](https://stytch.com?utm_source=supabase&utm_medium=guide) provides an all-in-one platform for passwordless auth. Stytch makes it easy for you to embed passwordless solutions into your websites and apps for better security, better conversion rates, and a better end user experience. Their easy-to-use SDKs and direct API access allows for maximum control and customization. In this example we will use [Email magic links](https://stytch.com/products/email-magic-links?utm_source=supabase&utm_medium=guide) to create and log in our users, and Session management. There is an additional, optional step to enable [Google One Tap](https://stytch.com/blog/improving-conversion-with-google-one-tap?utm_source=supabase&utm_medium=guide) which is an especially high-converting Google OAuth sign-up and login flow.
|
||
|
||
We will leverage Supabase to store and authorize access to user data. Supabase makes it simple to set up Row Level Security (RLS) policies which ensure users can only read and write data that they are authorized to do so. If you do not already have a Supabase account, you will need to create one.
|
||
|
||
This guide will use [Next.js](https://nextjs.org/) which is a web application framework built on top of React. Stytch provides a [Node.js library](https://github.com/stytchauth/stytch-node) and a [React library](https://github.com/stytchauth/stytch-react) which makes building Next.js apps super easy.
|
||
|
||
> Note: You can find a completed version of this project on [Github](https://github.com/stytchauth/stytch-nextjs-supabase).
|
||
|
||
## Step 0: Create a Stytch Account
|
||
|
||
If you already have a Stytch account you may skip this step.
|
||
|
||
Go to [Stytch](https://stytch.com?utm_source=supabase&utm_medium=guide), and create an account. Note that Stytch provides two ways to create an account, either via Google OAuth, or through Email magic links. This is the same user experience we will be building in this guide!
|
||
|
||

|
||
|
||
## Step 1: Set up Stytch redirect URLs
|
||
|
||
First we need to add the redirect URLs that will be used during the Email magic link flow. This step helps ensure bad actors cannot spoof your magic links and hijack redirects.
|
||
|
||
Navigate to your [redirect URL settings](https://stytch.com/dashboard/redirect-urls?utm_source=supabase&utm_medium=guide) in the Stytch dashboard, and under **Test environment** create an entry where the **URL** is `http://localhost:3000/api/authenticate` and the **Type** is `All`.
|
||
|
||

|
||
|
||
After pressing **Confirm**, the redirect URLs dashboard will update to show your new entry. We will use this URL later on.
|
||
|
||

|
||
|
||
## Step 2: Create a Supabase project
|
||
|
||
From your [Supabase dashboard](https://app.supabase.com/), click **New project**.
|
||
|
||
Enter a `Name` for your Supabase project.
|
||
|
||
Enter a secure `Database Password`.
|
||
|
||
Click **Create new project**. It may take a couple minutes for your project to be provisioned.
|
||
|
||

|
||
|
||
## Step 3: Creating data in Supabase
|
||
|
||
Once your Supabase project is provisioned, click Table editor, then New table. This tool is available from the sidebar menu in the [Supabase dashboard](https://app.supabase.com/).
|
||
|
||
Enter `expenses` as the **Name** field.
|
||
|
||
Select `Enable Row Level Security (RLS)`.
|
||
|
||
Add three new columns:
|
||
|
||
- `user_id` as `text`
|
||
|
||
- `title` as `text`
|
||
|
||
- `value` as `float8`
|
||
|
||
Click **Save** to create the new table.
|
||
|
||

|
||
|
||
From the Table editor view, select the expenses table and click **Insert row**.
|
||
|
||
Fill out the title and value fields (leave user_id blank for now) and click **Save**.
|
||
|
||

|
||
|
||
Use **Insert Row** to further populate the table with expenses.
|
||
|
||

|
||
|
||
## Step 4: Building a Next.js app
|
||
|
||
Using a terminal, create a new Next.js project:
|
||
|
||
```bash
|
||
npx create-next-app stytch-supabase-example
|
||
```
|
||
|
||
Next, within `stytch-supabase-example` create a `.env.local` file and enter the following values:
|
||
|
||
```
|
||
STYTCH_PROJECT_ENV=test
|
||
STYTCH_PROJECT_ID=GET_FROM_STYTCH_DASHBOARD
|
||
STYTCH_PUBLIC_TOKEN=GET_FROM_STYTCH_DASHBOARD
|
||
STYTCH_SECRET=GET_FROM_STYTCH_DASHBOARD
|
||
NEXT_PUBLIC_SUPABASE_URL=GET_FROM_SUPABASE_DASHBOARD
|
||
NEXT_PUBLIC_SUPABASE_KEY=GET_FROM_SUPABASE_DASHBOARD
|
||
SUPABASE_SIGNING_SECRET=GET_FROM_SUPABASE_DASHBOARD
|
||
```
|
||
|
||
> Note: Stytch values can be found in the project [dashboard](https://stytch.com/dashboard/api-keys?utm_source=supabase&utm_medium=guide) under **API Keys**.
|
||
|
||

|
||
|
||
> Note: Supabase values can be found under **Settings** > **API** for your project.
|
||
|
||

|
||
|
||
Start your Next.js development server to read in the new values from `.env.local`.
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
You should have a running Next.js application on `localhost:3000`.
|
||
|
||
## Step 5: Build the Login Form
|
||
|
||
Now we will replace the default Next.js home page with a login UI. We will use the Stytch React library.
|
||
|
||
> Note: Stytch provides direct API access for those that want to build login UI themselves
|
||
|
||
Install the `@stytch/stytch-react` library.
|
||
|
||
```bash
|
||
npm install @stytch/stytch-react
|
||
```
|
||
|
||
In the root directory, create a new folder named `components` and file in that folder named `/StytchLogin.js`. Within this file, paste the snippet below. This will configure, and style the Stytch React component to use Email magic links.
|
||
|
||
```jsx
|
||
// components/StytchLogin.js
|
||
import React from 'react'
|
||
import { Stytch } from '@stytch/stytch-react'
|
||
|
||
const stytchConfig = {
|
||
loginOrSignupView: {
|
||
products: ['emailMagicLinks'],
|
||
emailMagicLinksOptions: {
|
||
loginRedirectURL: 'http://localhost:3000/api/authenticate',
|
||
loginExpirationMinutes: 30,
|
||
signupRedirectURL: 'http://localhost:3000/api/authenticate',
|
||
signupExpirationMinutes: 30,
|
||
createUserAsPending: true,
|
||
},
|
||
},
|
||
style: {
|
||
fontFamily: '"Helvetica New", Helvetica, sans-serif',
|
||
width: '321px',
|
||
primaryColor: '#0577CA',
|
||
},
|
||
}
|
||
|
||
const StytchLogin = ({ publicToken }) => {
|
||
return (
|
||
<Stytch
|
||
publicToken={publicToken}
|
||
loginOrSignupView={stytchConfig.loginOrSignupView}
|
||
style={stytchConfig.style}
|
||
/>
|
||
)
|
||
}
|
||
|
||
export default StytchLogin
|
||
```
|
||
|
||
Additionally, create a profile component by creating a file called `Profile.js` in `/components`. We will use this component to render our expenses stored in Supabase later on.
|
||
|
||
```jsx
|
||
// components/Profile.js
|
||
import React from 'react'
|
||
import Link from 'next/link'
|
||
|
||
export default function Profile({ user }) {
|
||
return (
|
||
<div>
|
||
<h1>Welcome {user.userId}</h1>
|
||
<h2>Your expenses</h2>
|
||
{user.expenses?.length > 0 ? (
|
||
user.expenses.map((expense) => (
|
||
<p key={expense.id}>
|
||
{expense.title}: ${expense.value}
|
||
</p>
|
||
))
|
||
) : (
|
||
<p>You have no expenses!</p>
|
||
)}
|
||
|
||
<Link href="/api/logout" passHref>
|
||
<button>
|
||
<a>Logout</a>
|
||
</button>
|
||
</Link>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
Finally, replace the contents of the file `/pages/index.js` to render our new `StytchLogin` and `Profile` components.
|
||
|
||
```jsx
|
||
// pages/index.js
|
||
import styles from '../styles/Home.module.css'
|
||
import Profile from '../components/Profile'
|
||
import StytchLogin from '../components/StytchLogin'
|
||
|
||
const Index = ({ user, publicToken }) => {
|
||
let content
|
||
if (user) {
|
||
content = <Profile user={user} />
|
||
} else {
|
||
content = <StytchLogin publicToken={publicToken} />
|
||
}
|
||
|
||
return <div className={styles.main}>{content}</div>
|
||
}
|
||
|
||
export async function getServerSideProps({ req, res }) {
|
||
const user = null // Will update later
|
||
return {
|
||
props: { user, publicToken: process.env.STYTCH_PUBLIC_TOKEN },
|
||
}
|
||
}
|
||
|
||
export default Index
|
||
```
|
||
|
||
On `localhost:3000` there is now a login form prompting for your email address.
|
||
|
||

|
||
|
||
Enter your email address and press **Continue with email**.
|
||
|
||

|
||
|
||
In your inbox you will find a login request from your app.
|
||
|
||

|
||
|
||
However, if you click the link in the email you will get a 404. We need to build an API route to handle the email magic link authentication.
|
||
|
||
## Step 6: Authenticate and start a session
|
||
|
||
To make authentication easier we will use the Stytch Node.js library. Run
|
||
|
||
```bash
|
||
npm install stytch
|
||
```
|
||
|
||
Additionally, we will need to store the authenticated session in a cookie. Run
|
||
|
||
```bash
|
||
npm install cookies-next
|
||
```
|
||
|
||
Create a new folder named `utils` and inside a file named`stytchLogic.js` with the following contents
|
||
|
||
```jsx
|
||
// utils/stytchLogic.js
|
||
import * as stytch from 'stytch'
|
||
import { getCookie, setCookies, removeCookies } from 'cookies-next'
|
||
|
||
export const SESSION_COOKIE = 'stytch_cookie'
|
||
|
||
let client
|
||
const loadStytch = () => {
|
||
if (!client) {
|
||
client = new stytch.Client({
|
||
project_id: process.env.STYTCH_PROJECT_ID,
|
||
secret: process.env.STYTCH_SECRET,
|
||
env:
|
||
process.env.STYTCH_PROJECT_ENV === 'live'
|
||
? stytch.envs.live
|
||
: stytch.envs.test,
|
||
})
|
||
}
|
||
|
||
return client
|
||
}
|
||
|
||
export const getAuthenticatedUserFromSession = async (req, res) => {
|
||
const sessionToken = getCookie(SESSION_COOKIE, { req, res })
|
||
if (!sessionToken) {
|
||
return null
|
||
}
|
||
|
||
try {
|
||
const stytchClient = loadStytch()
|
||
const resp = await stytchClient.sessions.authenticate({
|
||
session_token: sessionToken,
|
||
})
|
||
return resp.session.user_id
|
||
} catch (error) {
|
||
console.log(error)
|
||
return null
|
||
}
|
||
}
|
||
|
||
export const revokeAndClearSession = async (req, res) => {
|
||
const sessionToken = getCookie(SESSION_COOKIE, { req, res })
|
||
|
||
if (sessionToken) {
|
||
try {
|
||
const stytchClient = loadStytch()
|
||
await stytchClient.sessions.revoke({
|
||
session_token: sessionToken,
|
||
})
|
||
} catch (error) {
|
||
console.log(error)
|
||
}
|
||
removeCookies(SESSION_COOKIE, { req, res })
|
||
}
|
||
|
||
return res.redirect('/')
|
||
}
|
||
|
||
export const authenticateTokenStartSession = async (req, res) => {
|
||
const { token, type } = req.query
|
||
let sessionToken
|
||
try {
|
||
const stytchClient = loadStytch()
|
||
const resp = await stytchClient.magicLinks.authenticate(token, {
|
||
session_duration_minutes: 30,
|
||
})
|
||
sessionToken = resp.session_token
|
||
} catch (error) {
|
||
console.log(error)
|
||
const errorString = JSON.stringify(error)
|
||
return res.status(400).json({ errorString })
|
||
}
|
||
|
||
setCookies(SESSION_COOKIE, sessionToken, {
|
||
req,
|
||
res,
|
||
maxAge: 60 * 60 * 24,
|
||
secure: true,
|
||
})
|
||
|
||
return res.redirect('/')
|
||
}
|
||
```
|
||
|
||
This logic is responsible for setting up the Stytch client we will use to call the API. It provides functions we will use to login, logout, and validate user sessions.
|
||
|
||
In order to complete the email login flow, create a new file `pages/api/authenticate.js` with the contents:
|
||
|
||
```jsx
|
||
// pages/api/authenticate.js
|
||
import { authenticateTokenStartSession } from '../../utils/stytchLogic'
|
||
|
||
export default async function handler(req, res) {
|
||
return authenticateTokenStartSession(req, res)
|
||
}
|
||
```
|
||
|
||
We will also create a logout API endpoint with similar contents. In `pages/api/logout.js` include the following:
|
||
|
||
```jsx
|
||
// pages/api/logout.js
|
||
import { revokeAndClearSession } from '../../utils/stytchLogic'
|
||
|
||
export default async function handler(req, res) {
|
||
return revokeAndClearSession(req, res)
|
||
}
|
||
```
|
||
|
||
Finally, update `pages/index.js` by importing `getAuthenticatedUserFromSession`, and calling it to set the user variable in `getServerSideProps`.
|
||
|
||
```jsx
|
||
// pages/index.js
|
||
import styles from '../styles/Home.module.css'
|
||
|
||
import StytchLogin from '../components/StytchLogin'
|
||
import Profile from '../components/Profile'
|
||
import { getAuthenticatedUserFromSession } from '../utils/stytchLogic'
|
||
|
||
const Index = ({ user, publicToken }) => {
|
||
let content
|
||
if (user) {
|
||
content = <Profile user={user} />
|
||
} else {
|
||
content = <StytchLogin publicToken={publicToken} />
|
||
}
|
||
|
||
return <div className={styles.main}>{content}</div>
|
||
}
|
||
|
||
export async function getServerSideProps({ req, res }) {
|
||
const userId = await getAuthenticatedUserFromSession(req, res)
|
||
if (userId) {
|
||
return {
|
||
props: { user: { userId }, publicToken: process.env.STYTCH_PUBLIC_TOKEN },
|
||
}
|
||
}
|
||
return {
|
||
props: { publicToken: process.env.STYTCH_PUBLIC_TOKEN },
|
||
}
|
||
}
|
||
|
||
export default Index
|
||
```
|
||
|
||
Return to `localhost:3000`, and login again by sending yourself a new email. Upon clicking through in the email you should be presented with “Welcome $USER_ID”. If you refresh the page, you should remain in an authenticated state. If you press **Logout** then you should return to the login screen.
|
||
|
||

|
||
|
||
Now that we have a working login flow with persistent authentication it is time to pull in our expense data from Supabase.
|
||
|
||
## Step 7: Requesting user data from Supabase
|
||
|
||
First, install the Supabase client:
|
||
|
||
```bash
|
||
npm install @supabase/supabase-js
|
||
```
|
||
|
||
In order to pass an authenticated `user_id` to Supabase we will package it within a JWT. Install jsonwebtoken:
|
||
|
||
```bash
|
||
npm install jsonwebtoken
|
||
```
|
||
|
||
Create a new file `utils/supabase.js` and add the following:
|
||
|
||
```jsx
|
||
// utils/supabase.js
|
||
import { createClient } from '@supabase/supabase-js'
|
||
import jwt from 'jsonwebtoken'
|
||
|
||
const getSupabase = (userId) => {
|
||
const supabase = createClient(
|
||
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||
process.env.NEXT_PUBLIC_SUPABASE_KEY
|
||
)
|
||
|
||
if (userId) {
|
||
const payload = {
|
||
userId,
|
||
exp: Math.floor(Date.now() / 1000) + 60 * 60,
|
||
}
|
||
|
||
supabase.auth.session = () => ({
|
||
access_token: jwt.sign(payload, process.env.SUPABASE_SIGNING_SECRET),
|
||
})
|
||
}
|
||
|
||
return supabase
|
||
}
|
||
|
||
export { getSupabase }
|
||
```
|
||
|
||
Our payload for the JWT will contain our user's unique identifier from Stytch, their `user_id`. We are signing this JWT using Supabase's signing secret, so Supabase will be able to validate it is authentic and hasn't been tampered with in transit.
|
||
|
||
Let's load our expenses from Supabase on the home page! Update `pages/index.js` a final time to make a request for expense data from Supabase.
|
||
|
||
```jsx
|
||
import styles from '../styles/Home.module.css'
|
||
|
||
import StytchLogin from '../components/StytchLogin'
|
||
import Profile from '../components/Profile'
|
||
import { getAuthenticatedUserFromSession } from '../utils/stytchLogic'
|
||
import { getSupabase } from '../utils/supabase'
|
||
|
||
const Index = ({ user, publicToken }) => {
|
||
let content
|
||
if (user) {
|
||
content = <Profile user={user} />
|
||
} else {
|
||
content = <StytchLogin publicToken={publicToken} />
|
||
}
|
||
|
||
return <div className={styles.main}>{content}</div>
|
||
}
|
||
|
||
export async function getServerSideProps({ req, res }) {
|
||
const userId = await getAuthenticatedUserFromSession(req, res)
|
||
|
||
if (userId) {
|
||
const supabase = getSupabase(userId)
|
||
const { data: expenses } = await supabase.from('expenses').select('*')
|
||
|
||
return {
|
||
props: {
|
||
user: { userId, expenses },
|
||
publicToken: process.env.STYTCH_PUBLIC_TOKEN,
|
||
},
|
||
}
|
||
} else {
|
||
return {
|
||
props: { publicToken: process.env.STYTCH_PUBLIC_TOKEN },
|
||
}
|
||
}
|
||
}
|
||
|
||
export default Index
|
||
```
|
||
|
||
When we reload our application, we are still getting the empty state for expenses.
|
||
|
||
This is because we enabled Row Level Security, which blocks all requests by default and lets you granularly control access to the data in your database. To enable our user to select their expenses we need to write a RLS policy.
|
||
|
||
## Step 8: Write a policy to allow select
|
||
|
||
Our policy will need to know who our currently logged in user is to determine whether or not they should have access. Let's create a PostgreSQL function to extract the current user from our new JWT.
|
||
|
||
Navigate back to the Supabase dashboard, select SQL from the sidebar menu, and click **New query**. This will create a new query,, which will allow us to run any SQL against our Postgres database.
|
||
|
||
Write the following and click **Run**.
|
||
|
||
```sql
|
||
create or replace function auth.user_id() returns text as $$
|
||
select nullif(current_setting('request.jwt.claims', true)::json->>'userId', '')::text;
|
||
$$ language sql stable;
|
||
```
|
||
|
||
You should see the output `Success, no rows returned`. This created a function called `auth.user_id()`, which will inspect the `userId` field of our JWT payload.
|
||
|
||
> Note: To learn more about PostgreSQL functions, check out this [deep dive video](https://www.youtube.com/watch?v=MJZCCpCYEqk).
|
||
|
||
Let's create a policy that checks whether this user is the owner of an expense.
|
||
|
||
Select **Authentication** from the Supabase sidebar menu, click **Policies**, then **New Policy**.
|
||
|
||

|
||
|
||
From the modal, select **For full customization create a policy from scratch** and add the following.
|
||
|
||

|
||
|
||
This policy is calling the function we just created to get the currently logged in user's `user_id` `auth.user_id()` and checking whether this matches the `user_id` column for the current expense. If it does, then it will allow the user to select it, otherwise it will continue to deny.
|
||
|
||
Click Review and then Save policy. After you've saved, click Enable RLS on the table to enable the policy we just created.
|
||
|
||
> Note: To learn more about RLS and policies, check out this [video](https://www.youtube.com/watch?v=Ow_Uzedfohk).
|
||
|
||
The last thing we need to do is update the `user_id` columns for our existing expenses.
|
||
|
||
Head back to the Supabase dashboard, and select Table editor from the sidebar. You will notice each entry has `user_id` set to `NULL`. We need to update this value to the proper `user_id`.
|
||
|
||

|
||
|
||
To get the `user_id` for our Stytch user, you can pull it from the welcome page in our example app (eg `user-test-61497d40-f957-45cd-a6c8-5408d22e93bc`).
|
||
|
||

|
||
|
||
Update each row in Supabase to this `user_id`.
|
||
|
||

|
||
|
||
Return to `localhost:3000`, and you will see your expenses listed.
|
||
|
||

|
||
|
||
We now have a basic expense tracker application powered by Stytch, Supabase, and Next.js. From here you could add additional features like adding, editing, and organizing your expenses further.
|
||
|
||
> Note: You can find a completed version of this project on [Github](https://github.com/stytchauth/stytch-nextjs-supabase).
|
||
|
||
## Optional: Add Google One Tap
|
||
|
||
In this optional step, we will extend our application to allow users to login with Google One Tap in addition to Email magic links.
|
||
|
||
You will need to follow the first four steps of [this guide](https://stytch.com/docs/one-tap-guide?utm_source=supabase&utm_medium=guide) to create a Google project, set up Google OAuth consent, and configure credentials and redirect URLs.
|
||
|
||
First, we will make some adjustments to the `StytchLogin` component. We will update the configuration, so that it uses both Google OAuth, and Email magic links.
|
||
|
||
```jsx
|
||
// components/StytchLogin.js
|
||
import React from 'react'
|
||
import { Stytch } from '@stytch/stytch-react'
|
||
|
||
const stytchConfig = {
|
||
loginOrSignupView: {
|
||
products: ['oauth', 'emailMagicLinks'],
|
||
oauthOptions: {
|
||
providers: [
|
||
{
|
||
type: 'google',
|
||
one_tap: true,
|
||
position: 'embedded',
|
||
},
|
||
],
|
||
loginRedirectURL: 'http://localhost:3000/api/authenticate?type=oauth',
|
||
signupRedirectURL: 'http://localhost:3000/api/authenticate?type=oauth',
|
||
},
|
||
emailMagicLinksOptions: {
|
||
loginRedirectURL: 'http://localhost:3000/api/authenticate',
|
||
loginExpirationMinutes: 30,
|
||
signupRedirectURL: 'http://localhost:3000/api/authenticate',
|
||
signupExpirationMinutes: 30,
|
||
createUserAsPending: true,
|
||
},
|
||
},
|
||
style: {
|
||
fontFamily: '"Helvetica New", Helvetica, sans-serif',
|
||
width: '321px',
|
||
primaryColor: '#0577CA',
|
||
},
|
||
}
|
||
|
||
const StytchLogin = ({ publicToken }) => {
|
||
return (
|
||
<Stytch
|
||
publicToken={publicToken}
|
||
loginOrSignupView={stytchConfig.loginOrSignupView}
|
||
style={stytchConfig.style}
|
||
/>
|
||
)
|
||
}
|
||
|
||
export default StytchLogin
|
||
```
|
||
|
||
We also need to make an adjustment to the function `authenticateTokenStartSession` in `stytchLogic.js`. Stytch has separate authentication endpoints for Email magic links and OAuth, so we need to route our token correctly.
|
||
|
||
```jsx
|
||
// utils/stytchLogic.js
|
||
|
||
// leave the rest of the file contents as is
|
||
export const authenticateTokenStartSession = async (req, res) => {
|
||
const { token, type } = req.query
|
||
let sessionToken
|
||
try {
|
||
const stytchClient = loadStytch()
|
||
if (type == 'oauth') {
|
||
const resp = await stytchClient.oauth.authenticate(token, {
|
||
session_duration_minutes: 30,
|
||
session_management_type: 'stytch',
|
||
})
|
||
sessionToken = resp.session.stytch_session.session_token
|
||
} else {
|
||
const resp = await stytchClient.magicLinks.authenticate(token, {
|
||
session_duration_minutes: 30,
|
||
})
|
||
sessionToken = resp.session_token
|
||
}
|
||
} catch (error) {
|
||
console.log(error)
|
||
const errorString = JSON.stringify(error)
|
||
return res.status(400).json({ errorString })
|
||
}
|
||
|
||
setCookies(SESSION_COOKIE, sessionToken, {
|
||
req,
|
||
res,
|
||
maxAge: 60 * 60 * 24,
|
||
secure: true,
|
||
})
|
||
|
||
return res.redirect('/')
|
||
}
|
||
```
|
||
|
||
With these two changes you will now have a working Google One Tap authentication method along with email magic links.
|
||
|
||

|
||
|
||
## Resources
|
||
|
||
- [Stytch blog](https://stytch.com/blog?utm_source=supabase&utm_medium=guide)
|
||
- [Stytch documentation](https://stytch.com/docs?utm_source=supabase&utm_medium=guide)
|