Skip to content

Commit 71f6311

Browse files
fix(oauth): correctly set internal protection value (#1962)
1 parent d04ce29 commit 71f6311

File tree

1 file changed

+111
-75
lines changed

1 file changed

+111
-75
lines changed

src/server/index.js

Lines changed: 111 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
1-
import adapters from '../adapters'
2-
import jwt from '../lib/jwt'
3-
import parseUrl from '../lib/parse-url'
4-
import logger, { setLogger } from '../lib/logger'
5-
import * as cookie from './lib/cookie'
6-
import * as defaultEvents from './lib/default-events'
7-
import * as defaultCallbacks from './lib/default-callbacks'
8-
import parseProviders from './lib/providers'
9-
import * as routes from './routes'
10-
import renderPage from './pages'
11-
import createSecret from './lib/create-secret'
12-
import callbackUrlHandler from './lib/callback-url-handler'
13-
import extendRes from './lib/extend-res'
14-
import csrfTokenHandler from './lib/csrf-token-handler'
15-
import * as pkce from './lib/oauth/pkce-handler'
16-
import * as state from './lib/oauth/state-handler'
1+
import adapters from "../adapters"
2+
import jwt from "../lib/jwt"
3+
import parseUrl from "../lib/parse-url"
4+
import logger, { setLogger } from "../lib/logger"
5+
import * as cookie from "./lib/cookie"
6+
import * as defaultEvents from "./lib/default-events"
7+
import * as defaultCallbacks from "./lib/default-callbacks"
8+
import parseProviders from "./lib/providers"
9+
import * as routes from "./routes"
10+
import renderPage from "./pages"
11+
import createSecret from "./lib/create-secret"
12+
import callbackUrlHandler from "./lib/callback-url-handler"
13+
import extendRes from "./lib/extend-res"
14+
import csrfTokenHandler from "./lib/csrf-token-handler"
15+
import * as pkce from "./lib/oauth/pkce-handler"
16+
import * as state from "./lib/oauth/state-handler"
1717

1818
// To work properly in production with OAuth providers the NEXTAUTH_URL
1919
// environment variable must be set.
2020
if (!process.env.NEXTAUTH_URL) {
21-
logger.warn('NEXTAUTH_URL', 'NEXTAUTH_URL environment variable not set')
21+
logger.warn("NEXTAUTH_URL", "NEXTAUTH_URL environment variable not set")
2222
}
2323

2424
/**
2525
* @param {import("next").NextApiRequest} req
2626
* @param {import("next").NextApiResponse} res
2727
* @param {import("types").NextAuthOptions} userOptions
2828
*/
29-
async function NextAuthHandler (req, res, userOptions) {
29+
async function NextAuthHandler(req, res, userOptions) {
3030
if (userOptions.logger) {
3131
setLogger(userOptions.logger)
3232
}
@@ -39,45 +39,64 @@ async function NextAuthHandler (req, res, userOptions) {
3939
// to avoid early termination of calls to the serverless function
4040
// (and then return that promise when we are done) - eslint
4141
// complains but I'm not sure there is another way to do this.
42-
return new Promise(async resolve => { // eslint-disable-line no-async-promise-executor
42+
// eslint-disable-next-line no-async-promise-executor
43+
return new Promise(async (resolve) => {
4344
extendRes(req, res, resolve)
4445

4546
if (!req.query.nextauth) {
46-
const error = 'Cannot find [...nextauth].js in pages/api/auth. Make sure the filename is written correctly.'
47+
const error =
48+
"Cannot find [...nextauth].js in pages/api/auth. Make sure the filename is written correctly."
4749

48-
logger.error('MISSING_NEXTAUTH_API_ROUTE_ERROR', error)
50+
logger.error("MISSING_NEXTAUTH_API_ROUTE_ERROR", error)
4951
return res.status(500).end(`Error: ${error}`)
5052
}
5153

5254
const {
5355
nextauth,
5456
action = nextauth[0],
5557
providerId = nextauth[1],
56-
error = nextauth[1]
58+
error = nextauth[1],
5759
} = req.query
5860

5961
// @todo refactor all existing references to baseUrl and basePath
60-
const { basePath, baseUrl } = parseUrl(process.env.NEXTAUTH_URL || process.env.VERCEL_URL)
62+
const { basePath, baseUrl } = parseUrl(
63+
process.env.NEXTAUTH_URL || process.env.VERCEL_URL
64+
)
6165

6266
const cookies = {
63-
...cookie.defaultCookies(userOptions.useSecureCookies || baseUrl.startsWith('https://')),
67+
...cookie.defaultCookies(
68+
userOptions.useSecureCookies || baseUrl.startsWith("https://")
69+
),
6470
// Allow user cookie options to override any cookie settings above
65-
...userOptions.cookies
71+
...userOptions.cookies,
6672
}
6773

6874
const secret = createSecret({ userOptions, basePath, baseUrl })
6975

70-
const providers = parseProviders({ providers: userOptions.providers, baseUrl, basePath })
76+
const providers = parseProviders({
77+
providers: userOptions.providers,
78+
baseUrl,
79+
basePath,
80+
})
7181
const provider = providers.find(({ id }) => id === providerId)
7282

7383
// Protection only works on OAuth 2.x providers
74-
if (provider?.type === 'oauth' && provider.version?.startsWith('2')) {
75-
// When provider.state is undefined, we still want this to pass
76-
if (!provider.protection && provider.state !== false) {
77-
// Default to state, as we did in 3.1 REVIEW: should we use "pkce" or "none" as default?
78-
provider.protection = ['state']
79-
} else if (typeof provider.protection === 'string') {
80-
provider.protection = [provider.protection]
84+
// TODO:
85+
// - rename to `checks` in 4.x, so it is similar to `openid-client`
86+
// - stop supporting `protection` as string
87+
// - remove `state` property
88+
if (provider?.type === "oauth" && provider.version?.startsWith("2")) {
89+
// Priority: (protection array > protection string) > state > default
90+
if (provider.protection) {
91+
provider.protection = Array.isArray(provider.protection)
92+
? provider.protection
93+
: [provider.protection]
94+
} else if (provider.state !== undefined) {
95+
provider.protection = [provider.state ? "state" : "none"]
96+
} else {
97+
// Default to state, as we did in 3.1
98+
// REVIEW: should we use "pkce" or "none" as default?
99+
provider.protection = ["state"]
81100
}
82101
}
83102

@@ -86,14 +105,16 @@ async function NextAuthHandler (req, res, userOptions) {
86105
// Parse database / adapter
87106
// If adapter is provided, use it (advanced usage, overrides database)
88107
// If database URI or config object is provided, use it (simple usage)
89-
const adapter = userOptions.adapter ?? (userOptions.database && adapters.Default(userOptions.database))
108+
const adapter =
109+
userOptions.adapter ??
110+
(userOptions.database && adapters.Default(userOptions.database))
90111

91112
// User provided options are overriden by other options,
92113
// except for the options with special handling above
93114
req.options = {
94115
debug: false,
95116
pages: {},
96-
theme: 'auto',
117+
theme: "auto",
97118
// Custom options override defaults
98119
...userOptions,
99120
// These computed settings can have values in userOptions but we override them
@@ -111,28 +132,28 @@ async function NextAuthHandler (req, res, userOptions) {
111132
jwt: !adapter, // If no adapter specified, force use of JSON Web Tokens (stateless)
112133
maxAge,
113134
updateAge: 24 * 60 * 60, // Sessions updated only if session is greater than this value (0 = always, 24*60*60 = every 24 hours)
114-
...userOptions.session
135+
...userOptions.session,
115136
},
116137
// JWT options
117138
jwt: {
118139
secret, // Use application secret if no keys specified
119140
maxAge, // same as session maxAge,
120141
encode: jwt.encode,
121142
decode: jwt.decode,
122-
...userOptions.jwt
143+
...userOptions.jwt,
123144
},
124145
// Event messages
125146
events: {
126147
...defaultEvents,
127-
...userOptions.events
148+
...userOptions.events,
128149
},
129150
// Callback functions
130151
callbacks: {
131152
...defaultCallbacks,
132-
...userOptions.callbacks
153+
...userOptions.callbacks,
133154
},
134155
pkce: {},
135-
logger
156+
logger,
136157
}
137158

138159
csrfTokenHandler(req, res)
@@ -141,64 +162,74 @@ async function NextAuthHandler (req, res, userOptions) {
141162
const render = renderPage(req, res)
142163
const { pages } = req.options
143164

144-
if (req.method === 'GET') {
165+
if (req.method === "GET") {
145166
switch (action) {
146-
case 'providers':
167+
case "providers":
147168
return routes.providers(req, res)
148-
case 'session':
169+
case "session":
149170
return routes.session(req, res)
150-
case 'csrf':
171+
case "csrf":
151172
return res.json({ csrfToken: req.options.csrfToken })
152-
case 'signin':
173+
case "signin":
153174
if (pages.signIn) {
154-
let signinUrl = `${pages.signIn}${pages.signIn.includes('?') ? '&' : '?'}callbackUrl=${req.options.callbackUrl}`
155-
if (error) { signinUrl = `${signinUrl}&error=${error}` }
175+
let signinUrl = `${pages.signIn}${
176+
pages.signIn.includes("?") ? "&" : "?"
177+
}callbackUrl=${req.options.callbackUrl}`
178+
if (error) {
179+
signinUrl = `${signinUrl}&error=${error}`
180+
}
156181
return res.redirect(signinUrl)
157182
}
158183

159184
return render.signin()
160-
case 'signout':
185+
case "signout":
161186
if (pages.signOut) return res.redirect(pages.signOut)
162187

163188
return render.signout()
164-
case 'callback':
189+
case "callback":
165190
if (provider) {
166191
if (await pkce.handleCallback(req, res)) return
167192
if (await state.handleCallback(req, res)) return
168193
return routes.callback(req, res)
169194
}
170195
break
171-
case 'verify-request':
196+
case "verify-request":
172197
if (pages.verifyRequest) {
173198
return res.redirect(pages.verifyRequest)
174199
}
175200
return render.verifyRequest()
176-
case 'error':
201+
case "error":
177202
if (pages.error) {
178-
return res.redirect(`${pages.error}${pages.error.includes('?') ? '&' : '?'}error=${error}`)
203+
return res.redirect(
204+
`${pages.error}${
205+
pages.error.includes("?") ? "&" : "?"
206+
}error=${error}`
207+
)
179208
}
180209

181210
// These error messages are displayed in line on the sign in page
182-
if ([
183-
'Signin',
184-
'OAuthSignin',
185-
'OAuthCallback',
186-
'OAuthCreateAccount',
187-
'EmailCreateAccount',
188-
'Callback',
189-
'OAuthAccountNotLinked',
190-
'EmailSignin',
191-
'CredentialsSignin'
192-
].includes(error)) {
211+
if (
212+
[
213+
"Signin",
214+
"OAuthSignin",
215+
"OAuthCallback",
216+
"OAuthCreateAccount",
217+
"EmailCreateAccount",
218+
"Callback",
219+
"OAuthAccountNotLinked",
220+
"EmailSignin",
221+
"CredentialsSignin",
222+
].includes(error)
223+
) {
193224
return res.redirect(`${baseUrl}${basePath}/signin?error=${error}`)
194225
}
195226

196227
return render.error({ error })
197228
default:
198229
}
199-
} else if (req.method === 'POST') {
230+
} else if (req.method === "POST") {
200231
switch (action) {
201-
case 'signin':
232+
case "signin":
202233
// Verified CSRF Token required for all sign in routes
203234
if (req.options.csrfTokenVerified && provider) {
204235
if (await pkce.handleSignin(req, res)) return
@@ -207,16 +238,19 @@ async function NextAuthHandler (req, res, userOptions) {
207238
}
208239

209240
return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`)
210-
case 'signout':
241+
case "signout":
211242
// Verified CSRF Token required for signout
212243
if (req.options.csrfTokenVerified) {
213244
return routes.signout(req, res)
214245
}
215246
return res.redirect(`${baseUrl}${basePath}/signout?csrf=true`)
216-
case 'callback':
247+
case "callback":
217248
if (provider) {
218249
// Verified CSRF Token required for credentials providers only
219-
if (provider.type === 'credentials' && !req.options.csrfTokenVerified) {
250+
if (
251+
provider.type === "credentials" &&
252+
!req.options.csrfTokenVerified
253+
) {
220254
return res.redirect(`${baseUrl}${basePath}/signin?csrf=true`)
221255
}
222256

@@ -225,31 +259,33 @@ async function NextAuthHandler (req, res, userOptions) {
225259
return routes.callback(req, res)
226260
}
227261
break
228-
case '_log':
262+
case "_log":
229263
if (userOptions.logger) {
230264
try {
231265
const {
232-
code = 'CLIENT_ERROR',
233-
level = 'error',
234-
message = '[]'
266+
code = "CLIENT_ERROR",
267+
level = "error",
268+
message = "[]",
235269
} = req.body
236270

237271
logger[level](code, ...JSON.parse(message))
238272
} catch (error) {
239273
// If logging itself failed...
240-
logger.error('LOGGER_ERROR', error)
274+
logger.error("LOGGER_ERROR", error)
241275
}
242276
}
243277
return res.end()
244278
default:
245279
}
246280
}
247-
return res.status(400).end(`Error: HTTP ${req.method} is not supported for ${req.url}`)
281+
return res
282+
.status(400)
283+
.end(`Error: HTTP ${req.method} is not supported for ${req.url}`)
248284
})
249285
}
250286

251287
/** Tha main entry point to next-auth */
252-
export default function NextAuth (...args) {
288+
export default function NextAuth(...args) {
253289
if (args.length === 1) {
254290
return (req, res) => NextAuthHandler(req, res, args[0])
255291
}

0 commit comments

Comments
 (0)