Skip to content

Commit ff57363

Browse files
authored
Add authentication example using Stytch (vercel#32194)
## Documentation / Examples This PR adds an example Next.js app that uses [Stytch](https://stytch.com/) for authentication, deployed on Vercel.
1 parent 265f71e commit ff57363

31 files changed

+1266
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# The two iron-session values may be left as-is while testing out this example app.
2+
IRON_SESSION_COOKIE_NAME="stytch_next_example_cookie"
3+
IRON_SESSION_PASSWORD="complex_password_at_least_32_characters_long"
4+
# The below values may be found in your Stytch Dashboard: https://stytch.com/dashboard/api-keys
5+
STYTCH_PROJECT_ENV=test
6+
STYTCH_PROJECT_ID="YOUR_STYTCH_PROJECT_ID"
7+
STYTCH_SECRET="YOUR_STYTCH_SECRET"
8+
NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN="YOUR_STYTCH_PUBLIC_TOKEN"
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
27+
# local env files
28+
.env.local
29+
.env.development.local
30+
.env.test.local
31+
.env.production.local
32+
33+
# vercel
34+
.vercel
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Stytch + Next.js example app on Vercel
2+
3+
This is a [Stytch](https://stytch.com) + [Next.js](https://nextjs.org/) project that showcases how to enable elegant authentication in your applicaiton.
4+
5+
<p align="center"><img src="./public/example-app-image.png" alt="stytch" width="50%"/></p>
6+
7+
In this repo, we have two sample auth flows:
8+
9+
- SDK integration: This flow uses Stytch's React component to create a login and sign-up flow using [Email Magic Links](https://stytch.com/docs/api/send-by-email).
10+
- API integration: This flow uses a custom UI with Stytch's backend API for [Onetime Passcodes(OTP) via SMS](https://stytch.com/docs/api/sms-otp-overview) authentication.
11+
12+
Both flows use Stytch's [Node client library](https://github.com/stytchauth/stytch-node) and [`iron-session`](https://github.com/vvo/next-iron-session) for session management.
13+
14+
**Note:** By default this example app enables three of our OAuth providers, Google, Microsoft, and Apple. If you haven't set up these OAuth providers in your [Dashboard](https://stytch.com/dashboard/oauth), you'll receive a redirect error when you attempt to login via those providers. You may remove all OAuth methods by removing `SDKProductTypes.oauth` from the `products` array in [pages/index.tsx](pages/index.tsx) or adjust which ones are displayed by via `oauthOptions.providers` in the same file. More detail on working with OAuth providers in our SDK may be found in our [Docs](https://stytch.com/docs/javascript-sdk#javascript-sdk/oauth).
15+
16+
# Deploy on Vercel
17+
18+
## Setting up Stytch
19+
20+
The first step is to configure the appropriate redirect URLs for your project. You'll set these magic link redirect URLs in the [Redirect URLs](https://stytch.com/dashboard/redirect-urls) section of your Dashboard. Add `https://*.vercel.app:3000` as both a login and sign-up redirect URL.
21+
22+
## Running the example app
23+
24+
Now just click the deploy button below! Once you're signed in to your Vercel account, you'll be guided through how to get up and running quickly. Check out [.env.template](/.env.template) for pointers on filling in the appropriate environment variables for this step.
25+
26+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Fblob%2Fcanary%2Fexamples%2Fauth-with-stytch%2F&env=STYTCH_PROJECT_ENV,STYTCH_PROJECT_ID,STYTCH_SECRET,NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN,IRON_SESSION_PASSWORD,IRON_SESSION_COOKIE_NAME&envDescription=All%20variables%20here%20need%20values%2C%20see%20the%20following%20link%20for%20pointers%20on%20how%20to%20feel%20these%20out.&envLink=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Fblob%2Fcanary%2Fexamples%2Fauth-with-stytch%2F.env.template&project-name=stytch-nextjs-vercel&repo-name=stytch-nextjs-vercel&demo-title=Stytch%20on%20Next.js%20with%20Vercel&demo-description=Next.js%20example%20app%20using%20Stytch%20authentication&demo-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Fblob%2Fcanary%2Fexamples%2Fauth-with-stytch&demo-image=https%3A%2F%2Fstytch.com%2Flogo-preview.png)
27+
28+
# Running locally via `vercel dev`
29+
30+
## Setting up Stytch
31+
32+
After signing up for Stytch, you'll need your Project's `project_id`, `secret`, and `public_token`. You can find these in the [API keys tab](https://stytch.com/dashboard/api-keys).
33+
34+
Once you've gathered these values, add them to a new .env.local file.
35+
Example:
36+
37+
```bash
38+
cp .env.template .env.local
39+
# Replace your keys in new .env.local file
40+
```
41+
42+
Next we'll configure the appropriate redirect URLs for your project, you'll set these magic link URLs for your project in the [Redirect URLs](https://stytch.com/dashboard/redirect-urls) section of your Dashboard. Add `http://localhost:3000/api/authenticate_magic_link` as both a login and sign-up redirect URL.
43+
44+
## Running the example app
45+
46+
Install dependencies by running
47+
48+
```bash
49+
npm install
50+
# or
51+
yarn install
52+
```
53+
54+
You can then run a development server using:
55+
56+
```bash
57+
vercel dev
58+
```
59+
60+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
61+
62+
## Documentation
63+
64+
Learn more about some of Stytch's products used in this example app:
65+
66+
- [Stytch React](https://www.npmjs.com/package/@stytch/stytch-react)
67+
- [Stytch's node client library](https://www.npmjs.com/package/stytch)
68+
- [iron-session](https://github.com/vvo/next-iron-session)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react'
2+
import styles from '../styles/Home.module.css'
3+
import { LoginMethod } from '../lib/types'
4+
import StytchContainer from './StytchContainer'
5+
6+
type Props = {
7+
setLoginMethod: (loginMethod: LoginMethod) => void
8+
}
9+
10+
const LoginEntryPoint = (props: Props) => {
11+
const { setLoginMethod } = props
12+
return (
13+
<StytchContainer>
14+
<h2>Hello Vercel!</h2>
15+
<p className={styles.entrySubHeader}>
16+
This example app demonstrates how you can integrate with Stytch using
17+
Next.js and deploy on Vercel. Now, let’s get started!
18+
</p>
19+
<button
20+
className={styles.entryButton}
21+
onClick={() => setLoginMethod(LoginMethod.SDK)}
22+
>
23+
SDK Integration (Email magic links)
24+
</button>
25+
<button
26+
className={styles.entryButton}
27+
onClick={() => setLoginMethod(LoginMethod.API)}
28+
>
29+
API Integration (SMS Passcodes)
30+
</button>
31+
</StytchContainer>
32+
)
33+
}
34+
35+
export default LoginEntryPoint
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React, { useState } from 'react'
2+
import SendOTPForm from './SendOTPForm'
3+
import VerifyOTPForm from './VerifyOTPForm'
4+
import StytchContainer from './StytchContainer'
5+
6+
const LoginWithSMS = () => {
7+
const [otpSent, setOTPSent] = useState(false)
8+
const [phoneNumber, setPhoneNumber] = useState('')
9+
const [methodId, setMethodId] = useState('')
10+
11+
return (
12+
<StytchContainer>
13+
{!otpSent ? (
14+
<SendOTPForm
15+
phoneNumber={phoneNumber}
16+
setMethodId={setMethodId}
17+
setOTPSent={setOTPSent}
18+
setPhoneNumber={setPhoneNumber}
19+
/>
20+
) : (
21+
<VerifyOTPForm methodId={methodId} phoneNumber={phoneNumber} />
22+
)}
23+
</StytchContainer>
24+
)
25+
}
26+
27+
export default LoginWithSMS
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import React from 'react'
2+
import { sendOTP } from '../lib/otpUtils'
3+
import styles from '../styles/Home.module.css'
4+
5+
type Props = {
6+
phoneNumber: string
7+
setMethodId: (methodId: string) => void
8+
setOTPSent: (submitted: boolean) => void
9+
setPhoneNumber: (phoneNumber: string) => void
10+
}
11+
12+
const SendOTPForm = (props: Props): JSX.Element => {
13+
const { phoneNumber, setMethodId, setOTPSent, setPhoneNumber } = props
14+
const [isDisabled, setIsDisabled] = React.useState(true)
15+
16+
const isValidNumber = (phoneNumberValue: string) => {
17+
// Regex validates phone numbers in (xxx)xxx-xxxx, xxx-xxx-xxxx, xxxxxxxxxx, and xxx.xxx.xxxx format
18+
const regex = /^[(]?[0-9]{3}[)]?[-s.]?[0-9]{3}[-s.]?[0-9]{4}$/g
19+
if (phoneNumberValue.match(regex)) {
20+
return true
21+
}
22+
return false
23+
}
24+
25+
const onPhoneNumberChange = (e: React.ChangeEvent<{ value: string }>) => {
26+
setPhoneNumber(e.target.value)
27+
if (isValidNumber(e.target.value)) {
28+
setIsDisabled(false)
29+
} else {
30+
setIsDisabled(true)
31+
}
32+
}
33+
34+
const onSubmit = async (e: React.FormEvent) => {
35+
e.preventDefault()
36+
if (isValidNumber(phoneNumber)) {
37+
const methodId = await sendOTP(phoneNumber)
38+
setMethodId(methodId)
39+
setOTPSent(true)
40+
}
41+
}
42+
43+
return (
44+
<div>
45+
<h2>Enter phone number</h2>
46+
<p className={styles.smsInstructions}>
47+
Enter your phone number to receive a passcode for authentication.
48+
</p>
49+
<form onSubmit={onSubmit}>
50+
<div className={styles.telInput}>
51+
<input
52+
className={styles.flag}
53+
name="intlCode"
54+
type="text"
55+
value="+1"
56+
readOnly
57+
/>
58+
<input
59+
id={styles.phoneNumber}
60+
className={styles.phoneNumber}
61+
placeholder="(123) 456-7890"
62+
value={phoneNumber}
63+
onChange={onPhoneNumberChange}
64+
type="tel"
65+
/>
66+
</div>
67+
<p className={styles.smsDisclaimer}>
68+
By continuing, you consent to receive an SMS for verification. Message
69+
and data rates may apply.
70+
</p>
71+
<input
72+
className={styles.primaryButton}
73+
disabled={isDisabled}
74+
id="button"
75+
type="submit"
76+
value="Continue"
77+
/>
78+
</form>
79+
</div>
80+
)
81+
}
82+
83+
export default SendOTPForm
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react'
2+
import styles from '../styles/Home.module.css'
3+
import Image from 'next/image'
4+
import lockup from '/public/powered-by-stytch.svg'
5+
6+
type Props = {
7+
children: React.ReactElement | React.ReactElement[]
8+
}
9+
10+
const StytchContainer = (props: Props) => {
11+
const { children } = props
12+
return (
13+
<div className={styles.container}>
14+
<div>{children}</div>
15+
<Image alt="Powered by Stytch" height={15} src={lockup} width={150} />
16+
</div>
17+
)
18+
}
19+
20+
export default StytchContainer

0 commit comments

Comments
 (0)