mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63171a0271 | ||
|
|
872e180339 | ||
|
|
a7709df796 | ||
|
|
dbe283f0fa | ||
|
|
727426bbec | ||
|
|
5a3ee47337 | ||
|
|
8dd8f7c48a | ||
|
|
072c59d85a | ||
|
|
d0e8147a48 | ||
|
|
5bc8f8b986 | ||
|
|
136361e1f4 | ||
|
|
cc9869592c | ||
|
|
073da60c3d | ||
|
|
aacc34bbfd | ||
|
|
074688d10e | ||
|
|
b3ffe50c03 | ||
|
|
e6d063825d | ||
|
|
985f7b3431 | ||
|
|
237b016378 | ||
|
|
776b9480da | ||
|
|
07a3f76cb3 | ||
|
|
3726d68c49 | ||
|
|
e31db1726a | ||
|
|
a241199c11 | ||
|
|
5385ec20a9 | ||
|
|
810d02e671 | ||
|
|
e5535734f8 | ||
|
|
ba7aed1057 | ||
|
|
a7e08e2a32 | ||
|
|
0d13040264 | ||
|
|
582520f8ef | ||
|
|
95942519a5 | ||
|
|
f3e64f04cc |
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -9,7 +9,7 @@ assignees: ''
|
||||
A clear and concise description of the feature being proposed.
|
||||
|
||||
**Purpose of proposed feature**
|
||||
A clear and concise description description of why this feature is necessary and what problems it solves.
|
||||
A clear and concise description of why this feature is necessary and what problems it solves.
|
||||
|
||||
**Detail about proposed feature**
|
||||
A detailed description of how the proposal might work (if you have one).
|
||||
|
||||
6
.github/labeler.yml
vendored
6
.github/labeler.yml
vendored
@@ -1,5 +1,6 @@
|
||||
test:
|
||||
- test/**/*
|
||||
- types/tests/**/*
|
||||
|
||||
documentation:
|
||||
- www/**/*
|
||||
@@ -32,4 +33,7 @@ client:
|
||||
|
||||
pages:
|
||||
- src/server/pages/**/*
|
||||
- www/docs/configuration/pages.md
|
||||
- www/docs/configuration/pages.md
|
||||
|
||||
TypeScript:
|
||||
- types/**/*
|
||||
|
||||
58
package-lock.json
generated
58
package-lock.json
generated
@@ -4450,6 +4450,12 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
|
||||
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
@@ -4801,11 +4807,6 @@
|
||||
"strip-ansi": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
|
||||
"integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg=="
|
||||
},
|
||||
"yargs": {
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
@@ -13875,8 +13876,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.0",
|
||||
"bundled": true,
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
|
||||
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
@@ -15042,6 +15044,12 @@
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
|
||||
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz",
|
||||
@@ -15675,9 +15683,9 @@
|
||||
"integrity": "sha512-WKrRpCSwL2t3tpOOGhf2WfTpcmbpxaWtDbdJdKdjd0aEiTkvOmS4NBkG6kzlaAHI9AkQ3iVqbFWM3Ei7mZ4o1Q=="
|
||||
},
|
||||
"preact-render-to-string": {
|
||||
"version": "5.1.7",
|
||||
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.1.7.tgz",
|
||||
"integrity": "sha512-3F4qvUsbiS/ZJ0lOHF+I8aye6x63QSXeOjaATJ6KppJsCUJW9adHa7CbBYX7Ib3DlYDp6PFwfefxK72NKys2sA==",
|
||||
"version": "5.1.14",
|
||||
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.1.14.tgz",
|
||||
"integrity": "sha512-xG/spHMnDX1cOOetZiFhljtczYUXqBrhuB+C2H+V0y3fJX8TmZtMrC+5di70y0E9fWAWiQIO5VTCpSDLoRmhzg==",
|
||||
"requires": {
|
||||
"pretty-format": "^3.8.0"
|
||||
}
|
||||
@@ -18834,11 +18842,6 @@
|
||||
"strip-ansi": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
|
||||
"integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg=="
|
||||
},
|
||||
"yargs": {
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
@@ -19624,9 +19627,9 @@
|
||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
|
||||
},
|
||||
"xmldom": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.3.0.tgz",
|
||||
"integrity": "sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g==",
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.5.0.tgz",
|
||||
"integrity": "sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA==",
|
||||
"dev": true
|
||||
},
|
||||
"xpath.js": {
|
||||
@@ -19642,10 +19645,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
||||
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
|
||||
"dev": true
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
|
||||
"integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg=="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
@@ -19812,6 +19814,12 @@
|
||||
"strip-ansi": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
|
||||
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "13.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
|
||||
@@ -19941,6 +19949,12 @@
|
||||
"strip-ansi": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
|
||||
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
"version": "14.2.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz",
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"oauth": "^0.9.15",
|
||||
"pkce-challenge": "^2.1.0",
|
||||
"preact": "^10.4.1",
|
||||
"preact-render-to-string": "^5.1.7",
|
||||
"preact-render-to-string": "^5.1.14",
|
||||
"querystring": "^0.2.0",
|
||||
"require_optional": "^1.0.1",
|
||||
"typeorm": "^0.2.30"
|
||||
|
||||
2
src/client/index.d.ts
vendored
2
src/client/index.d.ts
vendored
@@ -29,6 +29,8 @@ type GetSession<S extends Record<string, unknown> = DefaultSession> = (options:
|
||||
export interface NextAuthConfig {
|
||||
baseUrl: string
|
||||
basePath: string
|
||||
baseUrlServer: string
|
||||
basePathServer: string
|
||||
/** 0 means disabled (don't send); 60 means send every 60 seconds */
|
||||
keepAlive: number
|
||||
/** 0 means disabled (only use cache); 60 means sync if last checked > 60 seconds ago */
|
||||
|
||||
@@ -22,6 +22,8 @@ import parseUrl from '../lib/parse-url'
|
||||
const __NEXTAUTH = {
|
||||
baseUrl: parseUrl(process.env.NEXTAUTH_URL || process.env.VERCEL_URL).baseUrl,
|
||||
basePath: parseUrl(process.env.NEXTAUTH_URL).basePath,
|
||||
baseUrlServer: parseUrl(process.env.NEXTAUTH_URL_INTERNAL || process.env.NEXTAUTH_URL || process.env.VERCEL_URL).baseUrl,
|
||||
basePathServer: parseUrl(process.env.NEXTAUTH_URL_INTERNAL || process.env.NEXTAUTH_URL).basePath,
|
||||
keepAlive: 0,
|
||||
clientMaxAge: 0,
|
||||
// Properties starting with _ are used for tracking internal app state
|
||||
@@ -69,7 +71,12 @@ const SessionContext = createContext()
|
||||
*/
|
||||
export function useSession (session) {
|
||||
const context = useContext(SessionContext)
|
||||
const [data, setData] = useState(context?.[0] ?? session)
|
||||
if (context) return context
|
||||
return _useSessionHook(session)
|
||||
}
|
||||
|
||||
function _useSessionHook (session) {
|
||||
const [data, setData] = useState(session)
|
||||
const [loading, setLoading] = useState(!data)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -123,9 +130,9 @@ export function useSession (session) {
|
||||
__NEXTAUTH._clientSession = newClientSessionData
|
||||
|
||||
setData(newClientSessionData)
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
logger.error('CLIENT_USE_SESSION_ERROR', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
@@ -359,7 +366,7 @@ function _apiBaseUrl () {
|
||||
}
|
||||
|
||||
// Return absolute path when called server side
|
||||
return `${__NEXTAUTH.baseUrl}${__NEXTAUTH.basePath}`
|
||||
return `${__NEXTAUTH.baseUrlServer}${__NEXTAUTH.basePathServer}`
|
||||
}
|
||||
// Return relative path when called client side
|
||||
return __NEXTAUTH.basePath
|
||||
|
||||
25
src/providers/faceit.js
Normal file
25
src/providers/faceit.js
Normal file
@@ -0,0 +1,25 @@
|
||||
export default (options) => {
|
||||
return {
|
||||
id: 'faceit',
|
||||
name: 'FACEIT',
|
||||
type: 'oauth',
|
||||
version: '2.0',
|
||||
params: { grant_type: 'authorization_code' },
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from(`${options.clientId}:${options.clientSecret}`).toString('base64')}`
|
||||
},
|
||||
accessTokenUrl: 'https://api.faceit.com/auth/v1/oauth/token',
|
||||
authorizationUrl: 'https://accounts.faceit.com/accounts?redirect_popup=true&response_type=code',
|
||||
profileUrl: 'https://api.faceit.com/auth/v1/resources/userinfo',
|
||||
profile (profile) {
|
||||
const { guid: id, nickname: name, email, picture: image } = profile
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
email,
|
||||
image
|
||||
}
|
||||
},
|
||||
...options
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import Discord from './discord'
|
||||
import Email from './email'
|
||||
import EVEOnline from './eveonline'
|
||||
import Facebook from './facebook'
|
||||
import FACEIT from './faceit'
|
||||
import Foursquare from './foursquare'
|
||||
import FusionAuth from './fusionauth'
|
||||
import GitHub from './github'
|
||||
@@ -19,12 +20,14 @@ import GitLab from './gitlab'
|
||||
import Google from './google'
|
||||
import IdentityServer4 from './identity-server4'
|
||||
import Instagram from './instagram'
|
||||
import Kakao from './kakao'
|
||||
import LINE from './line'
|
||||
import LinkedIn from './linkedin'
|
||||
import MailRu from './mailru'
|
||||
import Medium from './medium'
|
||||
import Netlify from './netlify'
|
||||
import Okta from './okta'
|
||||
import Osso from './osso'
|
||||
import Reddit from './reddit'
|
||||
import Salesforce from './salesforce'
|
||||
import Slack from './slack'
|
||||
@@ -34,6 +37,7 @@ import Twitch from './twitch'
|
||||
import Twitter from './twitter'
|
||||
import VK from './vk'
|
||||
import Yandex from './yandex'
|
||||
import Zoho from './zoho'
|
||||
|
||||
export default {
|
||||
Apple,
|
||||
@@ -50,6 +54,7 @@ export default {
|
||||
Email,
|
||||
EVEOnline,
|
||||
Facebook,
|
||||
FACEIT,
|
||||
Foursquare,
|
||||
FusionAuth,
|
||||
GitHub,
|
||||
@@ -57,12 +62,14 @@ export default {
|
||||
Google,
|
||||
IdentityServer4,
|
||||
Instagram,
|
||||
Kakao,
|
||||
LINE,
|
||||
LinkedIn,
|
||||
MailRu,
|
||||
Medium,
|
||||
Netlify,
|
||||
Okta,
|
||||
Osso,
|
||||
Reddit,
|
||||
Salesforce,
|
||||
Slack,
|
||||
@@ -71,5 +78,6 @@ export default {
|
||||
Twitch,
|
||||
Twitter,
|
||||
VK,
|
||||
Yandex
|
||||
Yandex,
|
||||
Zoho
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ export default function Instagram (options) {
|
||||
email: null,
|
||||
image: null
|
||||
}
|
||||
}
|
||||
},
|
||||
...options
|
||||
}
|
||||
}
|
||||
|
||||
21
src/providers/kakao.js
Normal file
21
src/providers/kakao.js
Normal file
@@ -0,0 +1,21 @@
|
||||
export default (options) => {
|
||||
return {
|
||||
id: 'kakao',
|
||||
name: 'Kakao',
|
||||
type: 'oauth',
|
||||
version: '2.0',
|
||||
params: { grant_type: 'authorization_code' },
|
||||
accessTokenUrl: 'https://kauth.kakao.com/oauth/token',
|
||||
authorizationUrl: 'https://kauth.kakao.com/oauth/authorize?response_type=code',
|
||||
profileUrl: 'https://kapi.kakao.com/v2/user/me',
|
||||
profile: (profile) => {
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.kakao_account?.profile.nickname,
|
||||
email: profile.kakao_account?.email,
|
||||
image: profile.kakao_account?.profile.profile_image_url
|
||||
}
|
||||
},
|
||||
...options
|
||||
}
|
||||
}
|
||||
20
src/providers/osso.js
Normal file
20
src/providers/osso.js
Normal file
@@ -0,0 +1,20 @@
|
||||
export default (options) => {
|
||||
return {
|
||||
id: 'osso',
|
||||
name: 'SAML SSO',
|
||||
type: 'oauth',
|
||||
version: '2.0',
|
||||
params: { grant_type: 'authorization_code' },
|
||||
accessTokenUrl: `https://${options.domain}/oauth/token`,
|
||||
authorizationUrl: `https://${options.domain}/oauth/authorize?response_type=code`,
|
||||
profileUrl: `https://${options.domain}/oauth/me`,
|
||||
profile: (profile) => {
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.name || profile.email,
|
||||
email: profile.email
|
||||
}
|
||||
},
|
||||
...options
|
||||
}
|
||||
}
|
||||
22
src/providers/zoho.js
Normal file
22
src/providers/zoho.js
Normal file
@@ -0,0 +1,22 @@
|
||||
export default (options) => {
|
||||
return {
|
||||
id: 'zoho',
|
||||
name: 'Zoho',
|
||||
type: 'oauth',
|
||||
version: '2.0',
|
||||
scope: 'AaaServer.profile.Read',
|
||||
params: { grant_type: 'authorization_code' },
|
||||
accessTokenUrl: 'https://accounts.zoho.com/oauth/v2/token',
|
||||
authorizationUrl: 'https://accounts.zoho.com/oauth/v2/auth?response_type=code',
|
||||
profileUrl: 'https://accounts.zoho.com/oauth/user/info',
|
||||
profile: (profile) => {
|
||||
return {
|
||||
id: profile.ZUID,
|
||||
name: `${profile.First_Name} ${profile.Last_Name}`,
|
||||
email: profile.Email,
|
||||
image: null
|
||||
}
|
||||
},
|
||||
...options
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import * as defaultEvents from './lib/default-events'
|
||||
import * as defaultCallbacks from './lib/default-callbacks'
|
||||
import parseProviders from './lib/providers'
|
||||
import callbackUrlHandler from './lib/callback-url-handler'
|
||||
import extendRes from './lib/extend-req'
|
||||
import extendRes from './lib/extend-res'
|
||||
import * as routes from './routes'
|
||||
import renderPage from './pages'
|
||||
import csrfTokenHandler from './lib/csrf-token-handler'
|
||||
@@ -79,6 +79,10 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
provider.protection = 'state' // Default to state, as we did in 3.1 REVIEW: should we use "pkce" or "none" as default?
|
||||
}
|
||||
|
||||
if (typeof provider?.protection === 'string') {
|
||||
provider.protection = [provider.protection]
|
||||
}
|
||||
|
||||
const maxAge = 30 * 24 * 60 * 60 // Sessions expire after 30 days of being idle
|
||||
|
||||
// Parse database / adapter
|
||||
@@ -225,18 +229,19 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
}
|
||||
break
|
||||
case '_log':
|
||||
try {
|
||||
if (!userOptions.logger) return
|
||||
const {
|
||||
code = 'CLIENT_ERROR',
|
||||
level = 'error',
|
||||
message = '[]'
|
||||
} = req.body
|
||||
if (userOptions.logger) {
|
||||
try {
|
||||
const {
|
||||
code = 'CLIENT_ERROR',
|
||||
level = 'error',
|
||||
message = '[]'
|
||||
} = req.body
|
||||
|
||||
logger[level](code, ...JSON.parse(message))
|
||||
} catch (error) {
|
||||
// If logging itself failed...
|
||||
logger.error('LOGGER_ERROR', error)
|
||||
logger[level](code, ...JSON.parse(message))
|
||||
} catch (error) {
|
||||
// If logging itself failed...
|
||||
logger.error('LOGGER_ERROR', error)
|
||||
}
|
||||
}
|
||||
return res.end()
|
||||
default:
|
||||
|
||||
@@ -136,7 +136,7 @@ async function getOAuth2AccessToken (code, provider, codeVerifier) {
|
||||
headers.Authorization = `Bearer ${code}`
|
||||
}
|
||||
|
||||
if (provider.protection === 'pkce') {
|
||||
if (provider.protection.includes('pkce')) {
|
||||
params.code_verifier = codeVerifier
|
||||
}
|
||||
|
||||
@@ -167,9 +167,17 @@ async function getOAuth2AccessToken (code, provider, codeVerifier) {
|
||||
raw = querystring.parse(data)
|
||||
}
|
||||
|
||||
const accessToken = provider.id === 'slack'
|
||||
? raw.authed_user.access_token
|
||||
: raw.access_token
|
||||
let accessToken
|
||||
if (provider.id === 'slack') {
|
||||
const { ok, error } = raw
|
||||
if (!ok) {
|
||||
return reject(error)
|
||||
}
|
||||
|
||||
accessToken = raw.authed_user.access_token
|
||||
} else {
|
||||
accessToken = raw.access_token
|
||||
}
|
||||
|
||||
resolve({
|
||||
accessToken,
|
||||
|
||||
@@ -16,7 +16,7 @@ const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
|
||||
export async function handleCallback (req, res) {
|
||||
const { cookies, provider, baseUrl, basePath } = req.options
|
||||
try {
|
||||
if (provider.protection !== 'pkce') { // Provider does not support PKCE, nothing to do.
|
||||
if (!provider.protection.includes('pkce')) { // Provider does not support PKCE, nothing to do.
|
||||
return
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export async function handleCallback (req, res) {
|
||||
export async function handleSignin (req, res) {
|
||||
const { cookies, provider, baseUrl, basePath } = req.options
|
||||
try {
|
||||
if (provider.protection !== 'pkce') { // Provider does not support PKCE, nothing to do.
|
||||
if (!provider.protection.includes('pkce')) { // Provider does not support PKCE, nothing to do.
|
||||
return
|
||||
}
|
||||
// Started login flow, add generated pkce to req.options and (encrypted) code_verifier to a cookie
|
||||
|
||||
@@ -12,7 +12,7 @@ import { OAuthCallbackError } from '../../../lib/errors'
|
||||
export async function handleCallback (req, res) {
|
||||
const { csrfToken, provider, baseUrl, basePath } = req.options
|
||||
try {
|
||||
if (provider.protection !== 'state') { // Provider does not support state, nothing to do.
|
||||
if (!provider.protection.includes('state')) { // Provider does not support state, nothing to do.
|
||||
return
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export async function handleCallback (req, res) {
|
||||
export async function handleSignin (req, res) {
|
||||
const { provider, baseUrl, basePath, csrfToken } = req.options
|
||||
try {
|
||||
if (provider.protection !== 'state') { // Provider does not support state, nothing to do.
|
||||
if (![provider.protection].flat().includes('state')) { // Provider does not support state, nothing to do.
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// @ts-check
|
||||
import { h } from 'preact' // eslint-disable-line no-unused-vars
|
||||
import render from 'preact-render-to-string'
|
||||
|
||||
/**
|
||||
* Renders an error page.
|
||||
@@ -57,7 +56,7 @@ export default function error ({ baseUrl, basePath, error = 'default', res }) {
|
||||
|
||||
res.status(statusCode)
|
||||
|
||||
return render(
|
||||
return (
|
||||
<div className='error'>
|
||||
<h1>{heading}</h1>
|
||||
<div className='message'>{message}</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import renderToString from 'preact-render-to-string'
|
||||
import signin from './signin'
|
||||
import signout from './signout'
|
||||
import verifyRequest from './verify-request'
|
||||
@@ -10,7 +11,7 @@ export default function renderPage (req, res) {
|
||||
|
||||
res.setHeader('Content-Type', 'text/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>`)
|
||||
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">${renderToString(html)}</div></body></html>`)
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { h } from 'preact' // eslint-disable-line no-unused-vars
|
||||
import render from 'preact-render-to-string'
|
||||
|
||||
export default function signin ({ csrfToken, providers, callbackUrl, email, error: errorType }) {
|
||||
// We only want to render providers
|
||||
@@ -30,7 +29,7 @@ export default function signin ({ csrfToken, providers, callbackUrl, email, erro
|
||||
|
||||
const error = errorType && (errors[errorType] ?? errors.default)
|
||||
|
||||
return render(
|
||||
return (
|
||||
<div className='signin'>
|
||||
{error &&
|
||||
<div className='error'>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { h } from 'preact' // eslint-disable-line no-unused-vars
|
||||
import render from 'preact-render-to-string'
|
||||
|
||||
export default function signout ({ baseUrl, basePath, csrfToken }) {
|
||||
return render(
|
||||
return (
|
||||
<div className='signout'>
|
||||
<h1>Are you sure you want to sign out?</h1>
|
||||
<form action={`${baseUrl}${basePath}/signout`} method='POST'>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { h } from 'preact' // eslint-disable-line no-unused-vars
|
||||
import render from 'preact-render-to-string'
|
||||
|
||||
export default function verifyRequest ({ baseUrl }) {
|
||||
return render(
|
||||
return (
|
||||
<div className='verify-request'>
|
||||
<h1>Check your email</h1>
|
||||
<p>A sign in link has been sent to your email address.</p>
|
||||
|
||||
@@ -79,7 +79,7 @@ When using NextAuth.js without a database, the user object it will always be a p
|
||||
:::
|
||||
|
||||
:::tip
|
||||
If you only want to allow users who already have accounts in the database to sign in, you can check for the existance of a `user.id` property and reject any sign in attempts from accounts that do not have one.
|
||||
If you only want to allow users who already have accounts in the database to sign in, you can check for the existence of a `user.id` property and reject any sign in attempts from accounts that do not have one.
|
||||
|
||||
If you are using NextAuth.js without database and want to control who can sign in, you can check their email address or profile against a hard coded list in the `signIn()` callback.
|
||||
:::
|
||||
|
||||
@@ -18,9 +18,17 @@ If your Next.js application uses a custom base path, specify the route to the AP
|
||||
_e.g. `NEXTAUTH_URL=https://example.com/custom-route/api/auth`_
|
||||
|
||||
:::tip
|
||||
To set environment variables on Vercel, you can use the [dashboard](https://vercel.com/dashboard) or the `now env` command.
|
||||
To set environment variables on Vercel, you can use the [dashboard](https://vercel.com/dashboard) or the `vercel env` command.
|
||||
:::
|
||||
|
||||
### NEXTAUTH_URL_INTERNAL
|
||||
|
||||
If provided, server-side calls will use this instead of `NEXTAUTH_URL`. Useful in environments when the server doesn't have access to the canonical URL of your site. Defaults to `NEXTAUTH_URL`.
|
||||
|
||||
```
|
||||
NEXTAUTH_URL_INTERNAL=http://10.240.8.16
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Options
|
||||
@@ -113,11 +121,29 @@ By default JSON Web Tokens are signed (JWS) but not encrypted (JWE), as JWT encr
|
||||
jwt: {
|
||||
// A secret to use for key generation - you should set this explicitly
|
||||
// Defaults to NextAuth.js secret if not explicitly specified.
|
||||
// This is used to generate the actual signingKey and produces a warning
|
||||
// message if not defined explicitly.
|
||||
// secret: 'INp8IvdIyeMcoGAgFGoA61DdBglwwSqnXJZkgz8PSnw',
|
||||
|
||||
// You can generate a signing key using `jose newkey -s 512 -t oct -a HS512`
|
||||
// This gives you direct knowledge of the key used to sign the token so you can use it
|
||||
// to authenticate indirectly (eg. to a database driver)
|
||||
// signingKey: {"kty":"oct","kid":"Dl893BEV-iVE-x9EC52TDmlJUgGm9oZ99_ZL025Hc5Q","alg":"HS512","k":"K7QqRmJOKRK2qcCKV_pi9PSBv3XP0fpTu30TP8xn4w01xR3ZMZM38yL2DnTVPVw6e4yhdh0jtoah-i4c_pZagA"},
|
||||
|
||||
// If you chose something other than the default algorithm for the signingKey (HS512)
|
||||
// you also need to configure the algorithm
|
||||
// verificationOptions: {
|
||||
// algorithms: ['HS256']
|
||||
// },
|
||||
|
||||
// Set to true to use encryption. Defaults to false (signing only).
|
||||
// encryption: true,
|
||||
|
||||
// encryptionKey: "",
|
||||
// decryptionKey = encryptionKey,
|
||||
// decryptionOptions = {
|
||||
// algorithms: ['A256GCM']
|
||||
// },
|
||||
|
||||
// You can define your own encode/decode functions for signing and encryption
|
||||
// if you want to override the default behaviour.
|
||||
// async encode({ secret, token, maxAge }) {},
|
||||
|
||||
@@ -42,11 +42,22 @@ export default function SignIn({ providers }) {
|
||||
)
|
||||
}
|
||||
|
||||
// This is the recommended way for Next.js 9.3 or newer
|
||||
export async function getServerSideProps(context){
|
||||
const providers = await providers()
|
||||
return {
|
||||
props: { providers }
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// If older than Next.js 9.3
|
||||
SignIn.getInitialProps = async () => {
|
||||
return {
|
||||
providers: await providers()
|
||||
}
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
### Email Sign in
|
||||
@@ -54,7 +65,7 @@ SignIn.getInitialProps = async () => {
|
||||
If you create a custom sign in form for email sign in, you will need to submit both fields for the **email** address and **csrfToken** from **/api/auth/csrf** in a POST request to **/api/auth/signin/email**.
|
||||
|
||||
```jsx title="pages/auth/email-signin.js"
|
||||
import { csrfToken } from 'next-auth/client'
|
||||
import { getCsrfToken } from 'next-auth/client'
|
||||
|
||||
export default function SignIn({ csrfToken }) {
|
||||
return (
|
||||
@@ -62,18 +73,29 @@ export default function SignIn({ csrfToken }) {
|
||||
<input name='csrfToken' type='hidden' defaultValue={csrfToken}/>
|
||||
<label>
|
||||
Email address
|
||||
<input type='text' id='email' name='email'/>
|
||||
<input type='email' id='email' name='email'/>
|
||||
</label>
|
||||
<button type='submit'>Sign in with Email</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
SignIn.getInitialProps = async (context) => {
|
||||
// This is the recommended way for Next.js 9.3 or newer
|
||||
export async function getServerSideProps(context){
|
||||
const csrfToken = await getCsrfToken(context)
|
||||
return {
|
||||
csrfToken: await csrfToken(context)
|
||||
props: { csrfToken }
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// If older than Next.js 9.3
|
||||
SignIn.getInitialProps = async (context) => {
|
||||
return {
|
||||
csrfToken: await getCsrfToken(context)
|
||||
}
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
You can also use the `signIn()` function which will handle obtaining the CSRF token for you:
|
||||
@@ -87,7 +109,7 @@ signIn('email', { email: 'jsmith@example.com' })
|
||||
If you create a sign in form for credentials based authentication, you will need to pass a **csrfToken** from **/api/auth/csrf** in a POST request to **/api/auth/callback/credentials**.
|
||||
|
||||
```jsx title="pages/auth/credentials-signin.js"
|
||||
import { csrfToken } from 'next-auth/client'
|
||||
import { getCsrfToken } from 'next-auth/client'
|
||||
|
||||
export default function SignIn({ csrfToken }) {
|
||||
return (
|
||||
@@ -99,18 +121,30 @@ export default function SignIn({ csrfToken }) {
|
||||
</label>
|
||||
<label>
|
||||
Password
|
||||
<input name='password' type='text'/>
|
||||
<input name='password' type='password'/>
|
||||
</label>
|
||||
<button type='submit'>Sign in</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
SignIn.getInitialProps = async (context) => {
|
||||
// This is the recommended way for Next.js 9.3 or newer
|
||||
export async function getServerSideProps(context) {
|
||||
return {
|
||||
csrfToken: await csrfToken(context)
|
||||
props: {
|
||||
csrfToken: await getCsrfToken(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// If older than Next.js 9.3
|
||||
SignIn.getInitialProps = async (context) => {
|
||||
return {
|
||||
csrfToken: await getCsrfToken(context)
|
||||
}
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
You can also use the `signIn()` function which will handle obtaining the CSRF token for you:
|
||||
|
||||
@@ -56,15 +56,11 @@ NextAuth.js is designed to work with any OAuth service, it supports OAuth 1.0, 1
|
||||
|
||||
<Image src="/img/signin.png" alt="Signin Screenshot" />
|
||||
|
||||
:::tip
|
||||
If you want to create a custom sign in link you can link to **/api/auth/signin/[provider]** which will sign in the user in directly with that provider.
|
||||
:::
|
||||
|
||||
### Using a custom provider
|
||||
|
||||
You can use an OAuth provider that isn't built-in by using a custom object.
|
||||
|
||||
As an example of what this looks like, this is the the provider object returned for the Google provider:
|
||||
As an example of what this looks like, this is the provider object returned for the Google provider:
|
||||
|
||||
```js
|
||||
{
|
||||
@@ -145,7 +141,7 @@ You can look at the existing built-in providers for inspiration.
|
||||
| profile | An callback returning an object with the user's info | `object` | No |
|
||||
| idToken | Set to `true` for services that use ID Tokens (e.g. OpenID) | `boolean` | No |
|
||||
| headers | Any headers that should be sent to the OAuth provider | `object` | No |
|
||||
| protection | Additional security for OAuth login flows (defaults to `state`) | `pkce`, `state`, `none` | No |
|
||||
| protection | Additional security for OAuth login flows (defaults to `state`) |`[pkce]`,`[state]`,`[pkce,state]`| No |
|
||||
| state | Same as `protection: "state"`. Being deprecated, use protection. | `boolean` | No |
|
||||
|
||||
## Sign in with Email
|
||||
|
||||
@@ -76,13 +76,31 @@ In _most cases_ it does not make sense to specify a database in NextAuth.js opti
|
||||
The provider you tried to use failed when setting [PKCE or Proof Key for Code Exchange](https://tools.ietf.org/html/rfc7636#section-4.2).
|
||||
The `code_verifier` is saved in a cookie called (by default) `__Secure-next-auth.pkce.code_verifier` which expires after 15 minutes.
|
||||
Check if `cookies.pkceCodeVerifier` is configured correctly. The default `code_challenge_method` is `"S256"`. This is currently not configurable to `"plain"`, as it is not recommended, and in most cases it is only supported for backward compatibility.
|
||||
|
||||
---
|
||||
|
||||
### Session Handling
|
||||
|
||||
#### JWT_SESSION_ERROR
|
||||
|
||||
https://next-auth.js.org/errors#jwt_session_error JWKKeySupport: the key does not support HS512 verify algorithm
|
||||
|
||||
The algorithm used for generating your key isn't listed as supported. You can generate a HS512 key using
|
||||
|
||||
````
|
||||
jose newkey -s 512 -t oct -a HS512
|
||||
````
|
||||
|
||||
If you are unable to use an HS512 key (for example to interoperate with other services) you can define what is supported using
|
||||
|
||||
````
|
||||
jwt: {
|
||||
signingKey: {"kty":"oct","kid":"--","alg":"HS256","k":"--"}
|
||||
verificationOptions: {
|
||||
algorithms: ["HS256"]
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
#### SESSION_ERROR
|
||||
|
||||
---
|
||||
@@ -139,4 +157,4 @@ Check your mail server configuration.
|
||||
|
||||
This error happens when `[...nextauth].js` file is not found inside `pages/api/auth`.
|
||||
|
||||
Make sure the file is there and the filename is written correctly.
|
||||
Make sure the file is there and the filename is written correctly.
|
||||
|
||||
@@ -356,7 +356,7 @@ export default function App ({ Component, pageProps }) {
|
||||
:::note
|
||||
**These options have no effect on clients that are not signed in.**
|
||||
|
||||
Every tab/window maintains it's own copy of the local session state; the session it is not stored in shared storage like localStorage or sessionStorage. Any update in one tab/window triggers a message to other tabs/windows to update their own session state.
|
||||
Every tab/window maintains its own copy of the local session state; the session is not stored in shared storage like localStorage or sessionStorage. Any update in one tab/window triggers a message to other tabs/windows to update their own session state.
|
||||
|
||||
Using low values for `clientMaxAge` or `keepAlive` will increase network traffic and load on authenticated clients and may impact hosting costs and performance.
|
||||
:::
|
||||
|
||||
@@ -35,7 +35,7 @@ The `POST` submission requires CSRF token from `/api/auth/csrf`.
|
||||
|
||||
Returns client-safe session object - or an empty object if there is no session.
|
||||
|
||||
The contents of the session object that is returned is configurable with the session callback.
|
||||
The contents of the session object that is returned are configurable with the session callback.
|
||||
|
||||
#### `GET` /api/auth/csrf
|
||||
|
||||
@@ -52,7 +52,7 @@ It can be used to dynamically generate custom sign up pages and to check what ca
|
||||
---
|
||||
|
||||
:::note
|
||||
The default base path is `/api/auth` but it is configurable by specyfing a custom path in `NEXTAUTH_URL`
|
||||
The default base path is `/api/auth` but it is configurable by specifying a custom path in `NEXTAUTH_URL`
|
||||
|
||||
e.g.
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ providers: [
|
||||
Providers.Email({
|
||||
server: process.env.EMAIL_SERVER,
|
||||
from: process.env.EMAIL_FROM,
|
||||
sendVerificationRequest: ({ identifier: email, url, token, site, provider }) => { /* your function */ }
|
||||
sendVerificationRequest: ({ identifier: email, url, token, baseUrl, provider }) => { /* your function */ }
|
||||
})
|
||||
]
|
||||
```
|
||||
|
||||
30
www/docs/providers/faceit.md
Normal file
30
www/docs/providers/faceit.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
id: faceit
|
||||
title: FACEIT
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
https://cdn.faceit.com/third_party/docs/FACEIT_Connect_3.0.pdf
|
||||
|
||||
## Configuration
|
||||
|
||||
https://developers.faceit.com/apps
|
||||
|
||||
Grant type: `Authorization Code`
|
||||
|
||||
Scopes to have basic infos (email, nickname, guid and avatar) : `openid`, `email`, `profile`
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import Providers from `next-auth/providers`
|
||||
...
|
||||
providers: [
|
||||
Providers.FACEIT({
|
||||
clientId: process.env.FACEIT_CLIENT_ID,
|
||||
clientSecret: process.env.FACEIT_CLIENT_SECRET
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
32
www/docs/providers/kakao.md
Normal file
32
www/docs/providers/kakao.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
id: kakao
|
||||
title: Kakao
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
https://developers.kakao.com/product/kakaoLogin
|
||||
|
||||
## Configuration
|
||||
|
||||
https://developers.kakao.com/docs/latest/en/kakaologin/common
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import Providers from `next-auth/providers`
|
||||
...
|
||||
providers: [
|
||||
Providers.Kakao({
|
||||
clientId: process.env.KAKAO_CLIENT_ID,
|
||||
clientSecret: process.env.KAKAO_CLIENT_SECRET
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
## Instructions
|
||||
|
||||
### Configuration
|
||||
|
||||
Create a provider and a Kakao application at `https://developers.kakao.com/console/app`. In the settings of the app under Kakao Login, activate web app, change consent items and configure callback URL.
|
||||
39
www/docs/providers/osso.md
Normal file
39
www/docs/providers/osso.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: osso
|
||||
title: Osso
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
Osso is an open source service that handles SAML authentication against Identity Providers, normalizes profiles, and makes those profiles available to you in an OAuth 2.0 code grant flow.
|
||||
|
||||
If you don't yet have an Osso instance, you can use [Osso's Demo App](https://demo.ossoapp.com) for your testing purposes. For documentation on deploying an Osso instance, see https://ossoapp.com/docs/deploy/overview/
|
||||
|
||||
## Configuration
|
||||
|
||||
You can configure your OAuth Clients on your Osso Admin UI, i.e. https://demo.ossoapp.com/admin/config - you'll need to get a Client ID and Secret and allow-list your redirect URIs.
|
||||
|
||||
[SAML SSO differs a bit from OAuth](https://ossoapp.com/blog/saml-vs-oauth) - for every tenant who wants to sign in to your application using SAML, you and your customer need to perform a multi-step configuration in Osso's Admin UI and the admin dashboard of the tenant's Identity Provider. Osso provides documentation for providers like Okta and OneLogin, cloud-based IDPs who also offer a developer account that's useful for testing. Osso also provides a [Mock IDP](https://idp.ossoapp.com) that you can use for testing without needing to sign up for an Identity Provider service.
|
||||
|
||||
See Osso's complete configuration and testing documentation at https://ossoapp.com/docs/configure/overview
|
||||
|
||||
## Example
|
||||
|
||||
A full example application is available at https://github.com/enterprise-oss/osso-next-auth-example and https://nextjs-demo.ossoapp.com
|
||||
|
||||
```js
|
||||
import Providers from `next-auth/providers`
|
||||
...
|
||||
providers: [
|
||||
Providers.Osso({
|
||||
clientId: process.env.OSSO_CLIENT_ID,
|
||||
clientSecret: process.env.OSSO_CLIENT_SECRET,
|
||||
domain: process.env.OSSO_DOMAIN
|
||||
})
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
:::note
|
||||
`domain` should be the fully qualified domain – e.g. `demo.ossoapp.com`
|
||||
:::
|
||||
26
www/docs/providers/zoho.md
Normal file
26
www/docs/providers/zoho.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
id: zoho
|
||||
title: Zoho
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
https://www.zoho.com/accounts/protocol/oauth/web-server-applications.html
|
||||
|
||||
## Configuration
|
||||
|
||||
https://api-console.zoho.com/
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import Providers from `next-auth/providers`
|
||||
...
|
||||
providers: [
|
||||
Providers.Zoho({
|
||||
clientId: process.env.ZOHO_CLIENT_ID,
|
||||
clientSecret: process.env.ZOHO_CLIENT_SECRET
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
@@ -185,7 +185,7 @@ Once you have saved your schema, use the Prisma CLI to generate the Prisma Clien
|
||||
npx prisma generate
|
||||
```
|
||||
|
||||
To configure you database to use the new schema (i.e. create tables and columns) use the `primsa migrate` command:
|
||||
To configure you database to use the new schema (i.e. create tables and columns) use the `prisma migrate` command:
|
||||
|
||||
```
|
||||
npx prisma migrate dev --preview-feature
|
||||
|
||||
@@ -9,6 +9,14 @@ _These tutorials are contributed by the community and hosted on this site._
|
||||
|
||||
_New submissions and edits are welcome!_
|
||||
|
||||
### [NextJS Authentication Crash Course with NextAuth.js](https://youtu.be/o_wZIVmWteQ)
|
||||
|
||||
This tutorial dives in to the ins and outs of NextAuth including email, Github, Twitter and integrating with Auth0 in under hour.
|
||||
|
||||
### [Create your own NextAuth.js Login Pages](https://youtu.be/kB6YNYZ63fw)
|
||||
|
||||
This tutorial shows you how to jump in and create your own custom login pages versus using the ones provided by NextAuth.js
|
||||
|
||||
### [Refresh Token Rotation](tutorials/refresh-token-rotation)
|
||||
|
||||
How to implement refresh token rotation.
|
||||
|
||||
9895
www/package-lock.json
generated
9895
www/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user