mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
457952bb5a | ||
|
|
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 |
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
|
||||
```
|
||||
|
||||
52
package-lock.json
generated
52
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",
|
||||
@@ -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",
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -106,7 +106,8 @@ async function getToken (params) {
|
||||
// or not set (e.g. development or test instance) case use unprefixed name
|
||||
secureCookie = !(!process.env.NEXTAUTH_URL || process.env.NEXTAUTH_URL.startsWith('http://')),
|
||||
cookieName = (secureCookie) ? '__Secure-next-auth.session-token' : 'next-auth.session-token',
|
||||
raw = false
|
||||
raw = false,
|
||||
decode: _decode = decode
|
||||
} = params
|
||||
if (!req) throw new Error('Must pass `req` to JWT getToken()')
|
||||
|
||||
@@ -126,7 +127,7 @@ async function getToken (params) {
|
||||
}
|
||||
|
||||
try {
|
||||
return decode({ token, ...params })
|
||||
return _decode({ token, ...params })
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
|
||||
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'
|
||||
@@ -36,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,
|
||||
@@ -52,6 +54,7 @@ export default {
|
||||
Email,
|
||||
EVEOnline,
|
||||
Facebook,
|
||||
FACEIT,
|
||||
Foursquare,
|
||||
FusionAuth,
|
||||
GitHub,
|
||||
@@ -75,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
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -52,7 +52,7 @@ 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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
@@ -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
|
||||
|
||||
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