mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17b789822d | ||
|
|
fd12194c0c | ||
|
|
1c662e9ddc | ||
|
|
968903d227 | ||
|
|
3dedf6c26c | ||
|
|
d1dbfe1023 | ||
|
|
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 |
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/**/*
|
||||
|
||||
@@ -42,7 +42,7 @@ npm i
|
||||
> NOTE: You can add any environment variables to .env.local that you would like to use in your dev app.
|
||||
> You can find the next-auth config under`pages/api/auth/[...nextauth].js`.
|
||||
|
||||
1. Start the dev application/server and CSS watching:
|
||||
1. Start the dev application/server:
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
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",
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"build": "npm run build:js && npm run build:css",
|
||||
"build:js": "babel --config-file ./config/babel.config.json src --out-dir dist",
|
||||
"build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir dist && node config/wrap-css.js",
|
||||
"dev": "next | npm run watch:css",
|
||||
"dev:with-css": "next | npm run watch:css",
|
||||
"dev": "next",
|
||||
"watch": "npm run watch:js | npm run watch:css",
|
||||
"watch:js": "babel --config-file ./config/babel.config.json --watch src --out-dir dist",
|
||||
"watch:css": "postcss --config config/postcss.config.js --watch src/**/*.css --base src --dir dist",
|
||||
@@ -50,7 +51,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"
|
||||
|
||||
@@ -6,6 +6,27 @@ import Providers from 'next-auth/providers'
|
||||
// const prisma = new PrismaClient()
|
||||
|
||||
export default NextAuth({
|
||||
// Used to debug https://github.com/nextauthjs/next-auth/issues/1664
|
||||
// cookies: {
|
||||
// csrfToken: {
|
||||
// name: 'next-auth.csrf-token',
|
||||
// options: {
|
||||
// httpOnly: true,
|
||||
// sameSite: 'none',
|
||||
// path: '/',
|
||||
// secure: true
|
||||
// }
|
||||
// },
|
||||
// pkceCodeVerifier: {
|
||||
// name: 'next-auth.pkce.code_verifier',
|
||||
// options: {
|
||||
// httpOnly: true,
|
||||
// sameSite: 'none',
|
||||
// path: '/',
|
||||
// secure: true
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
providers: [
|
||||
Providers.Email({
|
||||
server: process.env.EMAIL_SERVER,
|
||||
@@ -19,6 +40,11 @@ export default NextAuth({
|
||||
clientId: process.env.AUTH0_ID,
|
||||
clientSecret: process.env.AUTH0_SECRET,
|
||||
domain: process.env.AUTH0_DOMAIN,
|
||||
// Used to debug https://github.com/nextauthjs/next-auth/issues/1664
|
||||
// protection: ["pkce", "state"],
|
||||
// authorizationParams: {
|
||||
// response_mode: 'form_post'
|
||||
// }
|
||||
protection: 'pkce'
|
||||
}),
|
||||
Providers.Twitter({
|
||||
|
||||
@@ -71,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(() => {
|
||||
@@ -125,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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
1
src/server/index.d.ts
vendored
1
src/server/index.d.ts
vendored
@@ -82,6 +82,7 @@ export interface NextAuthInternalOptions extends Pick<NextAuthOptions, NextAuthS
|
||||
basePath?: string
|
||||
action?: string
|
||||
csrfToken?: string
|
||||
csrfTokenVerified?: boolean
|
||||
}
|
||||
|
||||
export interface NextAuthRequest extends NextApiRequest {
|
||||
|
||||
@@ -6,12 +6,12 @@ import * as cookie from './lib/cookie'
|
||||
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 * as routes from './routes'
|
||||
import renderPage from './pages'
|
||||
import csrfTokenHandler from './lib/csrf-token-handler'
|
||||
import createSecret from './lib/create-secret'
|
||||
import callbackUrlHandler from './lib/callback-url-handler'
|
||||
import extendRes from './lib/extend-res'
|
||||
import csrfTokenHandler from './lib/csrf-token-handler'
|
||||
import * as pkce from './lib/oauth/pkce-handler'
|
||||
import * as state from './lib/oauth/state-handler'
|
||||
|
||||
@@ -67,16 +67,18 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
|
||||
const secret = createSecret({ userOptions, basePath, baseUrl })
|
||||
|
||||
const { csrfToken, csrfTokenVerified } = csrfTokenHandler(req, res, cookies, secret)
|
||||
|
||||
const providers = parseProviders({ providers: userOptions.providers, baseUrl, basePath })
|
||||
const provider = providers.find(({ id }) => id === providerId)
|
||||
|
||||
if (provider &&
|
||||
provider.type === 'oauth' && provider.version?.startsWith('2') &&
|
||||
(!provider.protection && provider.state !== false)
|
||||
) {
|
||||
provider.protection = 'state' // Default to state, as we did in 3.1 REVIEW: should we use "pkce" or "none" as default?
|
||||
// Protection only works on OAuth 2.x providers
|
||||
if (provider?.type === 'oauth' && provider.version?.startsWith('2')) {
|
||||
// When provider.state is undefined, we still want this to pass
|
||||
if (!provider.protection && provider.state !== false) {
|
||||
// Default to state, as we did in 3.1 REVIEW: should we use "pkce" or "none" as default?
|
||||
provider.protection = ['state']
|
||||
} else if (typeof provider.protection === 'string') {
|
||||
provider.protection = [provider.protection]
|
||||
}
|
||||
}
|
||||
|
||||
const maxAge = 30 * 24 * 60 * 60 // Sessions expire after 30 days of being idle
|
||||
@@ -103,7 +105,6 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
provider,
|
||||
cookies,
|
||||
secret,
|
||||
csrfToken,
|
||||
providers,
|
||||
// Session options
|
||||
session: {
|
||||
@@ -134,6 +135,7 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
logger
|
||||
}
|
||||
|
||||
csrfTokenHandler(req, res)
|
||||
await callbackUrlHandler(req, res)
|
||||
|
||||
const render = renderPage(req, res)
|
||||
@@ -146,7 +148,7 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
case 'session':
|
||||
return routes.session(req, res)
|
||||
case 'csrf':
|
||||
return res.json({ csrfToken })
|
||||
return res.json({ csrfToken: req.options.csrfToken })
|
||||
case 'signin':
|
||||
if (pages.signIn) {
|
||||
let signinUrl = `${pages.signIn}${pages.signIn.includes('?') ? '&' : '?'}callbackUrl=${req.options.callbackUrl}`
|
||||
@@ -199,7 +201,7 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
switch (action) {
|
||||
case 'signin':
|
||||
// Verified CSRF Token required for all sign in routes
|
||||
if (csrfTokenVerified && provider) {
|
||||
if (req.options.csrfTokenVerified && provider) {
|
||||
if (await pkce.handleSignin(req, res)) return
|
||||
if (await state.handleSignin(req, res)) return
|
||||
return routes.signin(req, res)
|
||||
@@ -208,14 +210,14 @@ async function NextAuthHandler (req, res, userOptions) {
|
||||
return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`)
|
||||
case 'signout':
|
||||
// Verified CSRF Token required for signout
|
||||
if (csrfTokenVerified) {
|
||||
if (req.options.csrfTokenVerified) {
|
||||
return routes.signout(req, res)
|
||||
}
|
||||
return res.redirect(`${baseUrl}${basePath}/signout?csrf=true`)
|
||||
case 'callback':
|
||||
if (provider) {
|
||||
// Verified CSRF Token required for credentials providers only
|
||||
if (provider.type === 'credentials' && !csrfTokenVerified) {
|
||||
if (provider.type === 'credentials' && !req.options.csrfTokenVerified) {
|
||||
return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`)
|
||||
}
|
||||
|
||||
@@ -225,18 +227,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:
|
||||
|
||||
@@ -14,29 +14,30 @@ import * as cookie from './cookie'
|
||||
* For more details, see the following OWASP links:
|
||||
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie
|
||||
* https://owasp.org/www-chapter-london/assets/slides/David_Johansson-Double_Defeat_of_Double-Submit_Cookie.pdf
|
||||
* @param {import("..").NextAuthRequest} req
|
||||
* @param {import("..").NextAuthResponse} res
|
||||
*/
|
||||
export default function csrfTokenHandler (req, res, cookies, secret) {
|
||||
const { csrfToken: csrfTokenFromRequest } = req.body
|
||||
|
||||
let csrfTokenFromCookie
|
||||
let csrfTokenVerified = false
|
||||
if (req.cookies[cookies.csrfToken.name]) {
|
||||
const [csrfTokenValue, csrfTokenHash] = req.cookies[cookies.csrfToken.name].split('|')
|
||||
if (csrfTokenHash === createHash('sha256').update(`${csrfTokenValue}${secret}`).digest('hex')) {
|
||||
export default function csrfTokenHandler (req, res) {
|
||||
const { cookies, secret } = req.options
|
||||
if (cookies.csrfToken.name in req.cookies) {
|
||||
const [csrfToken, csrfTokenHash] = req.cookies[cookies.csrfToken.name].split('|')
|
||||
const expectedCsrfTokenHash = createHash('sha256').update(`${csrfToken}${secret}`).digest('hex')
|
||||
if (csrfTokenHash === expectedCsrfTokenHash) {
|
||||
// If hash matches then we trust the CSRF token value
|
||||
csrfTokenFromCookie = csrfTokenValue
|
||||
|
||||
// If this is a POST request and the CSRF Token in the Post request matches
|
||||
// the cookie we have already verified is one we have set, then token is verified!
|
||||
if (req.method === 'POST' && csrfTokenFromCookie === csrfTokenFromRequest) { csrfTokenVerified = true }
|
||||
// If this is a POST request and the CSRF Token in the POST request matches
|
||||
// the cookie we have already verified is the one we have set, then the token is verified!
|
||||
const csrfTokenVerified = req.method === 'POST' && csrfToken === req.body.csrfToken
|
||||
req.options.csrfToken = csrfToken
|
||||
req.options.csrfTokenVerified = csrfTokenVerified
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!csrfTokenFromCookie) {
|
||||
// If no csrfToken - because it's not been set yet, or because the hash doesn't match
|
||||
// (e.g. because it's been modifed or because the secret has changed) create a new token.
|
||||
csrfTokenFromCookie = randomBytes(32).toString('hex')
|
||||
const newCsrfTokenCookie = `${csrfTokenFromCookie}|${createHash('sha256').update(`${csrfTokenFromCookie}${secret}`).digest('hex')}`
|
||||
cookie.set(res, cookies.csrfToken.name, newCsrfTokenCookie, cookies.csrfToken.options)
|
||||
}
|
||||
return { csrfToken: csrfTokenFromCookie, csrfTokenVerified }
|
||||
// If no csrfToken from cookie - because it's not been set yet,
|
||||
// or because the hash doesn't match (e.g. because it's been modifed or because the secret has changed)
|
||||
// create a new token.
|
||||
const csrfToken = randomBytes(32).toString('hex')
|
||||
const csrfTokenHash = createHash('sha256').update(`${csrfToken}${secret}`).digest('hex')
|
||||
const csrfTokenCookie = `${csrfToken}|${csrfTokenHash}`
|
||||
cookie.set(res, cookies.csrfToken.name, csrfTokenCookie, cookies.csrfToken.options)
|
||||
req.options.csrfToken = csrfToken
|
||||
}
|
||||
|
||||
@@ -59,14 +59,16 @@ export default async function oAuthCallback (req) {
|
||||
|
||||
try {
|
||||
// Handle OAuth v1.x
|
||||
const {
|
||||
oauth_token: oauthToken, oauth_verifier: oauthVerifier
|
||||
} = req.query
|
||||
const tokens = await client.getOAuthAccessToken(oauthToken, null, oauthVerifier)
|
||||
// eslint-disable-next-line camelcase
|
||||
const { oauth_token, oauth_verifier } = req.query
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
const { token_secret } = await client.getOAuthRequestToken(provider.params)
|
||||
const tokens = await client.getOAuthAccessToken(oauth_token, token_secret, oauth_verifier)
|
||||
const profileData = await client.get(
|
||||
provider.profileUrl,
|
||||
tokens.accessToken,
|
||||
tokens.refreshToken
|
||||
tokens.oauth_token,
|
||||
tokens.oauth_token_secret
|
||||
)
|
||||
|
||||
return getProfile({ profileData, tokens, provider })
|
||||
@@ -89,6 +91,10 @@ export default async function oAuthCallback (req) {
|
||||
* expires_in?: string | Date | null
|
||||
* refresh_token?: string
|
||||
* id_token?: string
|
||||
* token?: string
|
||||
* token_secret?: string
|
||||
* tokenSecret?: string
|
||||
* params?: any
|
||||
* }
|
||||
* provider: import("../..").Provider
|
||||
* user?: object
|
||||
|
||||
@@ -54,23 +54,36 @@ export default function oAuthClient (provider) {
|
||||
const originalGetOAuth1AccessToken = oauth1Client.getOAuthAccessToken.bind(oauth1Client)
|
||||
oauth1Client.getOAuthAccessToken = (...args) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
originalGetOAuth1AccessToken(...args, (error, accessToken, refreshToken, results) => {
|
||||
// eslint-disable-next-line camelcase
|
||||
originalGetOAuth1AccessToken(...args, (error, oauth_token, oauth_token_secret, params) => {
|
||||
if (error) {
|
||||
return reject(error)
|
||||
}
|
||||
resolve({ accessToken, refreshToken, results })
|
||||
|
||||
resolve({
|
||||
// TODO: Remove, this is only kept for backward compativility
|
||||
// These are not in the OAuth 1.x spec
|
||||
accessToken: oauth_token,
|
||||
refreshToken: oauth_token_secret,
|
||||
results: params,
|
||||
|
||||
oauth_token,
|
||||
oauth_token_secret,
|
||||
params
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const originalGetOAuthRequestToken = oauth1Client.getOAuthRequestToken.bind(oauth1Client)
|
||||
oauth1Client.getOAuthRequestToken = (...args) => {
|
||||
oauth1Client.getOAuthRequestToken = (params = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
originalGetOAuthRequestToken(...args, (error, oauthToken) => {
|
||||
// eslint-disable-next-line camelcase
|
||||
originalGetOAuthRequestToken(params, (error, oauth_token, oauth_token_secret, params) => {
|
||||
if (error) {
|
||||
return reject(error)
|
||||
}
|
||||
resolve(oauthToken)
|
||||
resolve({ oauth_token, oauth_token_secret, params })
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -136,7 +149,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 +180,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,8 @@ 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.
|
||||
// Provider does not support PKCE, nothing to do.
|
||||
if (!provider.protection?.includes('pkce')) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -50,7 +51,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,11 +12,12 @@ 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.
|
||||
// Provider does not support state, nothing to do.
|
||||
if (!provider.protection?.includes('state')) {
|
||||
return
|
||||
}
|
||||
|
||||
const { state } = req.query
|
||||
const state = req.query.state || req.body.state
|
||||
const expectedState = createHash('sha256').update(csrfToken).digest('hex')
|
||||
|
||||
logger.debug(
|
||||
@@ -41,7 +42,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?.includes('state')) { // Provider does not support state, nothing to do.
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -5,13 +5,17 @@ import logger from '../../../lib/logger'
|
||||
export default async function getAuthorizationUrl (req) {
|
||||
const { provider } = req.options
|
||||
|
||||
delete req.query?.nextauth
|
||||
const params = {
|
||||
...provider.authorizationParams,
|
||||
...req.query
|
||||
}
|
||||
|
||||
const client = oAuthClient(provider)
|
||||
if (provider.version?.startsWith('2.')) {
|
||||
delete req.query?.nextauth
|
||||
// Handle OAuth v2.x
|
||||
let url = client.getAuthorizeUrl({
|
||||
...provider.authorizationParams,
|
||||
...req.query,
|
||||
...params,
|
||||
redirect_uri: provider.callbackUrl,
|
||||
scope: provider.scope
|
||||
})
|
||||
@@ -34,8 +38,12 @@ export default async function getAuthorizationUrl (req) {
|
||||
}
|
||||
|
||||
try {
|
||||
const oAuthToken = await client.getOAuthRequestToken()
|
||||
const url = `${provider.authorizationUrl}?oauth_token=${oAuthToken}`
|
||||
const tokens = await client.getOAuthRequestToken(params)
|
||||
const url = `${provider.authorizationUrl}?${new URLSearchParams({
|
||||
oauth_token: tokens.oauth_token,
|
||||
oauth_token_secret: tokens.oauth_token_secret,
|
||||
...tokens.params
|
||||
})}`
|
||||
logger.debug('GET_AUTHORIZATION_URL', url)
|
||||
return url
|
||||
} catch (error) {
|
||||
|
||||
@@ -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.
|
||||
@@ -53,11 +52,11 @@ export default function error ({ baseUrl, basePath, error = 'default', res }) {
|
||||
}
|
||||
}
|
||||
|
||||
const { statusCode, heading, message, signin } = errors[error.toLowerCase()]
|
||||
const { statusCode, heading, message, signin } = errors[error.toLowerCase()] ?? errors.default
|
||||
|
||||
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,7 +18,7 @@ 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
|
||||
@@ -121,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.
|
||||
@@ -11,6 +11,10 @@ https://docs.microsoft.com/en-us/linkedin/shared/authentication/authorization-co
|
||||
|
||||
https://www.linkedin.com/developers/apps/
|
||||
|
||||
From the Auth tab get the client ID and client secret. On the same tab, add redirect URLs such as http://localhost:3000/api/auth/callback/linkedin so LinkedIn can correctly redirect back to your application. Finally, head over to the Products tab and enable the "Sign In with LinkedIn" product. The LinkedIn team will review and approve your request before you can test it out.
|
||||
|
||||

|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
|
||||
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