Compare commits

...

17 Commits

Author SHA1 Message Date
Baterka
960ca85907 fix: send only the error message in callback redirects (#1424)
Changed `encodeURIComponent(error)` to `encodeURIComponent(error.message)` to remove prefix (such as `Error: ` and possible stack trace).
Seems like better way of doing it and also safer if server throws some error with sensitive data.
2021-03-05 18:34:11 +01:00
Balázs Orbán
f960cc0f6f chore(docs): upgrade docs dependencies 2021-03-03 20:13:46 +01:00
Balázs Orbán
0f64f3eea7 chore: don't mark bugs as stale
Had a good laugh today 😄:

"This is not stale. Bread goes stale. Bugs don't. They don't just magically go away because time has passed" - Unknown
https://twitter.com/ericclemmons/status/1367000259046604803
2021-03-03 19:54:13 +01:00
yannicktian
71c78e8e24 feat(provider): allow disabling redirection on sign in with email (#1416)
* feat: allow to disable client-side redirect for email provider

* docs(client): mention that redirect can also be disabled for email provider

* feat: only display one email input in email page
2021-03-02 22:38:02 +01:00
dependabot[bot]
d86609a2dc chore(deps): bump prismjs from 1.22.0 to 1.23.0 in /www (#1409)
Bumps [prismjs](https://github.com/PrismJS/prism) from 1.22.0 to 1.23.0.
- [Release notes](https://github.com/PrismJS/prism/releases)
- [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PrismJS/prism/compare/v1.22.0...v1.23.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-01 22:52:46 +01:00
Joost van Wollingen
d0c3400d30 docs(page): Remove unnecessary context param (#1406)
...when calling providers in the docs
2021-03-01 21:39:03 +01:00
Praneeth
172e79cb04 fix(page): add character encoding and page titles (#1380)
* added character encoding fix

* changed multi-line to inline and added title param to send fn in src/server/pages/index.js

* modified the return object of renderPage in src/server/pages/index.js
2021-03-01 21:17:51 +01:00
Balázs Orbán
46d5c76605 docs: reword callbacks.md
Explain the `jwt()` callback before the `session()` callback, as it comes first in the flow.
2021-02-28 18:05:10 +01:00
Zach White
438efd8a9b docs: reword pages.md (#1386)
language edits
2021-02-27 23:43:45 +01:00
Balázs Orbán
d8d497cc91 feat(provider): call generateVerificationToken async (#1378) 2021-02-27 23:33:26 +01:00
Pop Stefan
6152c8afbb docs: added refresh token tutorial link in faq page (#1385) 2021-02-27 20:24:09 +01:00
Balázs Orbán
5ae6f6118c docs: add missing comma
Thx @followbl 😺
2021-02-25 23:29:33 +01:00
sid
96ff048b59 fix(provider): use correct file type for Discord profile img (#1365) 2021-02-23 21:39:27 +01:00
Ariel Weingarten
e80f6e936d docs(provider): Update twitch.md (#1353)
State what redirect URL to add to the Twitch console.
2021-02-22 20:04:38 +01:00
Lawrence Chen
6b5a215fb2 docs(tutorials): refresh token rotation (#1310)
* docs(tutorials): refresh token rotation

* use simple initialization

* be optimistic

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* add yarn.lock to .gitignore

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-02-21 22:30:01 +01:00
Balázs Orbán
782482b9f4 feat: make tokens available in profile callback (#1329)
* feat: make access_token available in profile callback

* docs(provider): mention access_token param in profile callback

* feat: send all available tokens to provider.profile
2021-02-20 22:58:48 +01:00
Balázs Orbán
2d364f246a docs: tweak release badges 2021-02-17 19:14:45 +01:00
24 changed files with 350 additions and 83 deletions

1
.github/stale.yml vendored
View File

@@ -7,6 +7,7 @@ exemptLabels:
- pinned
- security
- priority
- bug
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable

4
.gitignore vendored
View File

@@ -11,6 +11,8 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn.lock
# Dependencies
node_modules
@@ -37,4 +39,4 @@ www/providers.json
/_work
# Prisma migrations
/prisma/migrations
/prisma/migrations

View File

@@ -23,9 +23,9 @@
<img src="https://img.shields.io/github/stars/nextauthjs/next-auth" alt="Github Stars" />
</a>
<a href="https://www.npmjs.com/package/next-auth">
<img src="https://img.shields.io/github/v/release/nextauthjs/next-auth" alt="Github Stable Release" />
<img src="https://img.shields.io/github/v/release/nextauthjs/next-auth?label=latest" alt="Github Stable Release" />
</a>
<img src="https://img.shields.io/github/v/release/nextauthjs/next-auth?include_prereleases" alt="Github Prelease" />
<img src="https://img.shields.io/github/v/release/nextauthjs/next-auth?include_prereleases&label=prerelease&sort=semver" alt="Github Prelease" />
</p>
</p>

View File

@@ -100,6 +100,11 @@ export default function Header () {
<a>Credentials</a>
</Link>
</li>
<li className={styles.navItem}>
<Link href='/email'>
<a>Email</a>
</Link>
</li>
</ul>
</nav>
</header>

View File

@@ -111,8 +111,8 @@
},
"funding": [
{
"type" : "github",
"url" : "https://github.com/sponsors/balazsorban44"
"type": "github",
"url": "https://github.com/sponsors/balazsorban44"
}
]
}

66
pages/email.js Normal file
View File

@@ -0,0 +1,66 @@
import * as React from 'react'
import { signIn, signOut, useSession } from 'next-auth/client'
import Layout from 'components/layout'
export default function Page () {
const [response, setResponse] = React.useState(null)
const [email, setEmail] = React.useState('')
const handleChange = (event) => {
setEmail(event.target.value)
}
const handleLogin = (options) => async (event) => {
event.preventDefault()
if (options.redirect) {
return signIn('email', options)
}
const response = await signIn('email', options)
setResponse(response)
}
const handleLogout = (options) => async (event) => {
if (options.redirect) {
return signOut(options)
}
const response = await signOut(options)
setResponse(response)
}
const [session] = useSession()
if (session) {
return (
<Layout>
<h1>Test different flows for Email logout</h1>
<span className='spacing'>Default:</span>
<button onClick={handleLogout({ redirect: true })}>Logout</button><br />
<span className='spacing'>No redirect:</span>
<button onClick={handleLogout({ redirect: false })}>Logout</button><br />
<p>Response:</p>
<pre style={{ background: '#eee', padding: 16 }}>{JSON.stringify(response, null, 2)}</pre>
</Layout>
)
}
return (
<Layout>
<h1>Test different flows for Email login</h1>
<label className='spacing'>
Email address:{' '}
<input type='text' id='email' name='email' value={email} onChange={handleChange} />
</label><br />
<form onSubmit={handleLogin({ redirect: true, email })}>
<span className='spacing'>Default:</span>
<button type='submit'>Sign in with Email</button>
</form>
<form onSubmit={handleLogin({ redirect: false, email })}>
<span className='spacing'>No redirect:</span>
<button type='submit'>Sign in with Email</button>
</form>
<p>Response:</p>
<pre style={{ background: '#eee', padding: 16 }}>{JSON.stringify(response, null, 2)}</pre>
</Layout>
)
}

View File

@@ -258,6 +258,9 @@ export async function signIn (provider, options = {}, authorizationParams = {})
return
}
const isCredentials = providers[provider].type === 'credentials'
const isEmail = providers[provider].type === 'email'
const canRedirectBeDisabled = isCredentials || isEmail
const signInUrl = isCredentials
? `${baseUrl}/callback/${provider}`
: `${baseUrl}/signin/${provider}`
@@ -279,7 +282,7 @@ export async function signIn (provider, options = {}, authorizationParams = {})
const _signInUrl = `${signInUrl}?${new URLSearchParams(authorizationParams)}`
const res = await fetch(_signInUrl, fetchOptions)
const data = await res.json()
if (redirect || !isCredentials) {
if (redirect || !canRedirectBeDisabled) {
const url = data.url ?? callbackUrl
window.location = url
// If url contains a hash, the browser does not reload the page. We reload manually

View File

@@ -14,7 +14,7 @@ export default (options) => {
const defaultAvatarNumber = parseInt(profile.discriminator) % 5
profile.image_url = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarNumber}.png`
} else {
const format = profile.premium_type === 1 || profile.premium_type === 2 ? 'gif' : 'png'
const format = profile.avatar.startsWith('a_') ? 'gif' : 'png'
profile.image_url = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}`
}
return {

View File

@@ -108,7 +108,7 @@ async function getProfile ({ profileData, tokens, provider, user }) {
logger.debug('PROFILE_DATA', profileData)
const profile = await provider.profile(profileData)
const profile = await provider.profile(profileData, tokens)
// Return profile, raw profile and auth provider details
return {
profile: {

View File

@@ -10,7 +10,7 @@ export default async function email (email, provider, options) {
const secret = provider.secret || options.secret
// Generate token
const token = provider.generateVerificationToken?.() ?? randomBytes(32).toString('hex')
const token = await provider.generateVerificationToken?.() ?? randomBytes(32).toString('hex')
// Send email with link containing token (the unhashed version)
const url = `${baseUrl}${basePath}/callback/${encodeURIComponent(provider.id)}?email=${encodeURIComponent(email)}&token=${encodeURIComponent(token)}`

View File

@@ -9,14 +9,34 @@ export default function renderPage (req, res) {
const { baseUrl, basePath, callbackUrl, csrfToken, providers, theme } = req.options
res.setHeader('Content-Type', 'text/html')
function send (html) {
res.send(`<!DOCTYPE html><head><style type="text/css">${css()}</style><meta name="viewport" content="width=device-width, initial-scale=1"></head><body class="__next-auth-theme-${theme}"><div class="page">${html}</div></body></html>`)
function send ({ html, title }) {
res.send(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>${css()}</style><title>${title}</title></head><body class="__next-auth-theme-${theme}"><div class="page">${html}</div></body></html>`)
}
return {
signin (props) { send(signin({ csrfToken, providers, callbackUrl, ...req.query, ...props })) },
signout (props) { send(signout({ csrfToken, baseUrl, basePath, ...props })) },
verifyRequest (props) { send(verifyRequest({ baseUrl, ...props })) },
error (props) { send(error({ basePath, baseUrl, res, ...props })) }
signin (props) {
send({
html: signin({ csrfToken, providers, callbackUrl, ...req.query, ...props }),
title: 'Sign In'
})
},
signout (props) {
send({
html: signout({ csrfToken, baseUrl, basePath, ...props }),
title: 'Sign Out'
})
},
verifyRequest (props) {
send({
html: verifyRequest({ baseUrl, ...props }),
title: 'Verify Request'
})
},
error (props) {
send({
html: error({ basePath, baseUrl, res, ...props }),
title: 'Error'
})
}
}
}

View File

@@ -72,7 +72,7 @@ export default async function callback (req, res) {
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`)
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
}
// TODO: Remove in a future major release
logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT')
@@ -168,7 +168,7 @@ export default async function callback (req, res) {
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`)
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
}
// TODO: Remove in a future major release
logger.warn('SIGNIN_CALLBACK_REJECT_REDIRECT')
@@ -239,7 +239,7 @@ export default async function callback (req, res) {
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`)
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
}
return res.redirect(error)
}
@@ -254,7 +254,7 @@ export default async function callback (req, res) {
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`)
return res.redirect(`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`)
}
return res.redirect(error)
}

View File

@@ -112,52 +112,6 @@ callbacks: {
The redirect callback may be invoked more than once in the same flow.
:::
## Session callback
The session callback is called whenever a session is checked.
e.g. `getSession()`, `useSession()`, `/api/auth/session`
* When using database sessions, the User object is passed as an argument.
* When using JSON Web Tokens for sessions, the JWT payload is provided instead.
```js title="pages/api/auth/[...nextauth].js"
...
callbacks: {
/**
* @param {object} session Session object
* @param {object} token User object (if using database sessions)
* JSON Web Token (if not using database sessions)
* @return {object} Session that will be returned to the client
*/
async session(session, token) {
if(token?.accessToken) {
// Add property to session, like an access_token from a provider
session.accessToken = token.accessToken
}
return session
}
}
...
```
:::tip
When using JSON Web Tokens the `jwt()` callback is invoked before the `session()` callback, so anything you add to the
JSON Web Token will be immediately available in the session callback, like for example an `access_token` from a provider.
:::
:::tip
To better represent its value, when using a JWT session, the second parameter should be called `token` (This is the same thing you return from the `jwt` callback). If you use a database, call it `user`.
:::
:::warning
The session object is not persisted server side, even when using database sessions - only data such as the session token, the user, and the expiry time is stored in the session table.
If you need to persist session data server side, you can use the `accessToken` returned for the session as a key - and connect to the database in the `session()` callback to access it. Session `accessToken` values do not rotate and are valid as long as the session is valid.
If using JSON Web Tokens instead of database sessions, you should use the User ID or a unique key stored in the token (you will need to generate a key for this yourself on sign in, as access tokens for sessions are not generated when using JSON Web Tokens).
:::
## JWT callback
This JSON Web Token callback is called whenever a JSON Web Token is created (i.e. at sign
@@ -206,3 +160,47 @@ NextAuth.js does not limit how much data you can store in a JSON Web Token, howe
If you need to persist a large amount of data, you will need to persist it elsewhere (e.g. in a database). You can store a key that can be used to look up that data in the `session()` callback.
:::
## Session callback
The session callback is called whenever a session is checked. By default, only a subset of the token is returned for increased security. If you want to make something available you added to the token through the `jwt()` callback, you have to explicitely forward it here to make it available to the client.
e.g. `getSession()`, `useSession()`, `/api/auth/session`
* When using database sessions, the User object is passed as an argument.
* When using JSON Web Tokens for sessions, the JWT payload is provided instead.
```js title="pages/api/auth/[...nextauth].js"
...
callbacks: {
/**
* @param {object} session Session object
* @param {object} token User object (if using database sessions)
* JSON Web Token (if not using database sessions)
* @return {object} Session that will be returned to the client
*/
async session(session, token) {
// Add property to session, like an access_token from a provider.
session.accessToken = token.accessToken
return session
}
}
...
```
:::tip
When using JSON Web Tokens the `jwt()` callback is invoked before the `session()` callback, so anything you add to the
JSON Web Token will be immediately available in the session callback, like for example an `access_token` from a provider.
:::
:::tip
To better represent its value, when using a JWT session, the second parameter should be called `token` (This is the same thing you return from the `jwt()` callback). If you use a database, call it `user`.
:::
:::warning
The session object is not persisted server side, even when using database sessions - only data such as the session token, the user, and the expiry time is stored in the session table.
If you need to persist session data server side, you can use the `accessToken` returned for the session as a key - and connect to the database in the `session()` callback to access it. Session `accessToken` values do not rotate and are valid as long as the session is valid.
If using JSON Web Tokens instead of database sessions, you should use the User ID or a unique key stored in the token (you will need to generate a key for this yourself on sign in, as access tokens for sessions are not generated when using JSON Web Tokens).
:::

View File

@@ -328,7 +328,7 @@ export default NextAuth({
},
warn(code, ...message) {
log.warn(code, message)
}
},
debug(code, ...message) {
log.debug(code, message)
}

View File

@@ -7,7 +7,7 @@ NextAuth.js automatically creates simple, unbranded authentication pages for han
The options displayed on the sign up page are automatically generated based on the providers specified in the options passed to NextAuth.js.
To add a custom login page, for example. You can use the `pages` option:
To add a custom login page, you can use the `pages` option:
```javascript title="pages/api/auth/[...nextauth].js"
...
@@ -42,9 +42,9 @@ export default function SignIn({ providers }) {
)
}
SignIn.getInitialProps = async (context) => {
SignIn.getInitialProps = async () => {
return {
providers: await providers(context)
providers: await providers()
}
}
```
@@ -121,4 +121,4 @@ signIn('credentials', { username: 'jsmith', password: '1234' })
:::tip
Remember to put any custom pages in a folder outside **/pages/api** which is reserved for API code. As per the examples above, a location convention suggestion is `pages/auth/...`.
:::
:::

View File

@@ -78,7 +78,10 @@ As an example of what this looks like, this is the the provider object returned
requestTokenUrl: "https://accounts.google.com/o/oauth2/auth",
authorizationUrl: "https://accounts.google.com/o/oauth2/auth?response_type=code",
profileUrl: "https://www.googleapis.com/oauth2/v1/userinfo?alt=json",
async profile(profile) {
async profile(profile, tokens) {
// You can use the tokens, in case you want to fetch more profile information
// For example several OAuth provider does not return e-mail by default.
// Depending on your provider, will have tokens like `access_token`, `id_token` and or `refresh_token`
return {
id: profile.id,
name: profile.name,

View File

@@ -116,7 +116,7 @@ NextAuth.js records Refresh Tokens and Access Tokens on sign in (if supplied by
You can then look them up from the database or persist them to the JSON Web Token.
Note: NextAuth.js does not currently handle Access Token rotation for OAuth providers for you, if this is something you need, currently you will need to write the logic to handle that yourself.
Note: NextAuth.js does not currently handle Access Token rotation for OAuth providers for you, however you can check out [this tutorial](/tutorials/refresh-token-rotation) if you want to implement it.
### When I sign in with another account with the same email address, why are accounts not linked automatically?

View File

@@ -212,7 +212,18 @@ The URL must be considered valid by the [redirect callback handler](/configurati
#### Using the redirect: false option
When you use the `credentials` provider, you might not want the user to redirect to an error page if an error occurs, so you can handle any errors (like wrong credentials given by the user) on the same page. For that, you can pass `redirect: false` in the second parameter object. `signIn` then will return a Promise, that resolves to the following:
:::note
The redirect option is only available for `credentials` and `email` providers.
:::
In some cases, you might want to deal with the sign in response on the same page and disable the default redirection. For example, if an error occurs (like wrong credentials given by the user), you might want to handle the error on the same page. For that, you can pass `redirect: false` in the second parameter object.
e.g.
- `signIn('credentials', { redirect: false, password: 'password' })`
- `signIn('email', { redirect: false, email: 'bill@fillmurray.com' })`
`signIn` will then return a Promise, that resolves to the following:
```ts
{

View File

@@ -184,3 +184,17 @@ const text = ({ url, site }) => `Sign in to ${site}\n${url}\n\n`
:::tip
If you want to generate great looking email client compatible HTML with React, check out https://mjml.io
:::
## Customising the Verification Token
By default, we are generating a random verification token. You can define a `generateVerificationToken` method in your provider options if you want to override it:
```js title="pages/api/auth/[...nextauth].js"
providers: [
Providers.Email({
async generateVerificationToken() {
return "ABC123"
}
})
],

View File

@@ -11,6 +11,8 @@ https://dev.twitch.tv/docs/authentication
https://dev.twitch.tv/console/apps
Add the following redirect URL into the console `http://<your-next-app-url>/api/auth/callback/twitch`
## Example
```js
@@ -23,4 +25,4 @@ providers: [
})
]
...
```
```

View File

@@ -9,6 +9,10 @@ _These tutorials are contributed by the community and hosted on this site._
_New submissions and edits are welcome!_
### [Refresh Token Rotation](tutorials/refresh-token-rotation)
How to implement refresh token rotation.
### [Securing pages and API routes](tutorials/securing-pages-and-api-routes)
How to restrict access to pages and API routes.
@@ -67,7 +71,6 @@ This example shows how to implement a fullstack app in TypeScript with Next.js u
This `dev.to` tutorial walks one through adding NextAuth.js to an existing project. Including setting up the OAuth client id and secret, adding the API routes for authentication, protecting pages and api routes behind that authentication, etc.
### [Adding Sign in With Apple Next JS](https://thesiddd.com/blog/apple-auth)
This tutorial walks step by step on how to get Sign In with Apple working (both locally and on a deployed website) using NextAuth.js.

View File

@@ -0,0 +1,139 @@
---
id: refresh-token-rotation
title: Refresh Token Rotation
---
While NextAuth.js doesn't automatically handle access token rotation for OAuth providers yet, this functionality can be implemented using [callbacks](https://next-auth.js.org/configuration/callbacks).
## Source Code
_A working example can be accessed [here](https://github.com/lawrencecchen/next-auth-refresh-tokens)._
## Implementation
### Server Side
Using a [JWT callback](https://next-auth.js.org/configuration/callbacks#jwt-callback) and a [session callback](https://next-auth.js.org/configuration/callbacks#session-callback), we can persist OAuth tokens and refresh them when they expire.
Below is a sample implementation using Google's Identity Provider. Please note that the OAuth 2.0 request in the `refreshAccessToken()` function will vary between different providers, but the core logic should remain similar.
```js title="pages/auth/[...nextauth.js]"
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
const GOOGLE_AUTHORIZATION_URL =
"https://accounts.google.com/o/oauth2/v2/auth?" +
new URLSearchParams({
prompt: "consent",
access_type: "offline",
response_type: "code",
});
/**
* Takes a token, and returns a new token with updated
* `accessToken` and `accessTokenExpires`. If an error occurs,
* returns the old token and an error property
*/
async function refreshAccessToken(token) {
try {
const url =
"https://oauth2.googleapis.com/token?" +
new URLSearchParams({
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
grant_type: "refresh_token",
refresh_token: token.refreshToken,
});
const response = await fetch(url, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
method: "POST",
});
const refreshedTokens = await response.json();
if (!response.ok) {
throw refreshedTokens;
}
return {
...token,
accessToken: refreshedTokens.access_token,
accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, // Fall back to old refresh token
};
} catch (error) {
console.log(error);
return {
...token,
error: "RefreshAccessTokenError",
};
}
}
export default NextAuth({
providers: [
Providers.Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorizationUrl: GOOGLE_AUTHORIZATION_URL,
}),
],
callbacks: {
async jwt(token, user, account) {
// Initial sign in
if (account && user) {
return {
accessToken: account.accessToken,
accessTokenExpires: Date.now() + account.expires_in * 1000,
refreshToken: account.refresh_token,
user,
};
}
// Return previous token if the access token has not expired yet
if (Date.now() < token.accessTokenExpires) {
return token;
}
// Access token has expired, try to update it
return refreshAccessToken(token);
},
async session(session, token) {
if (token) {
session.user = token.user;
session.accessToken = token.accessToken;
session.error = token.error;
}
return session;
},
},
});
```
### Client Side
The `RefreshAccessTokenError` error that is caught in the `refreshAccessToken()` method is passed all the way to the client. This means that you can direct the user to the sign in flow if we cannot refresh their token.
We can handle this functionality as a side effect:
```js title="pages/auth/[...nextauth.js]"
import { signIn, useSession } from "next-auth/client";
import { useEffect } from "react";
const HomePage() {
const [session] = useSession();
useEffect(() => {
if (session?.error === "RefreshAccessTokenError") {
signIn(); // Force sign in to hopefully resolve error
}
}, [session]);
return (...)
}
```

6
www/package-lock.json generated
View File

@@ -10900,9 +10900,9 @@
"integrity": "sha512-MgMhSdHuHymNRqD6KM3eGS0PNqgK9q4QF5P0yoQQvpB6jNjeSAi3jcSAz0Sua/t9fa4xDOMar9HJbLa08gl9ug=="
},
"prismjs": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.22.0.tgz",
"integrity": "sha512-lLJ/Wt9yy0AiSYBf212kK3mM5L8ycwlyTlSxHBAneXLR0nzFMlZ5y7riFPF3E33zXOF2IH95xdY5jIyZbM9z/w==",
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz",
"integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==",
"requires": {
"clipboard": "^2.0.0"
}

View File

@@ -11,16 +11,16 @@
"generate-providers": "node ./scripts/generate-providers.js"
},
"dependencies": {
"@docusaurus/core": "^2.0.0-alpha.66",
"@docusaurus/preset-classic": "^2.0.0-alpha.66",
"@docusaurus/core": "^2.0.0-alpha.70",
"@docusaurus/preset-classic": "^2.0.0-alpha.70",
"classnames": "^2.2.6",
"docusaurus-lunr-search": "^2.1.7",
"docusaurus-lunr-search": "^2.1.10",
"jose": "^2.0.2",
"lodash.times": "^4.3.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-marquee-slider": "^1.1.2",
"styled-components": "^5.2.0"
"styled-components": "^5.2.1"
},
"browserslist": {
"production": [
@@ -35,6 +35,6 @@
]
},
"devDependencies": {
"standard": "^15.0.0"
"standard": "^16.0.3"
}
}