diff --git a/docs/schemes/oauth2.md b/docs/schemes/oauth2.md index db7b45a47..db078d133 100644 --- a/docs/schemes/oauth2.md +++ b/docs/schemes/oauth2.md @@ -86,3 +86,32 @@ By default is set to `refresh_token_key: 'refresh_token'`. It automatically stor By default is set to random generated string. The primary reason for using the state parameter is to mitigate CSRF attacks. ([read more](https://auth0.com/docs/protocols/oauth2/oauth-state)) + +### `_runtimeOptions` + +`NOTE: Only available in univeral mode` + + +By default all options are baked into the bundle during build. +If you use the same bundle in multiple environments (e.g. test and prod), but have options that are environment-specific, +you can supply a function like this: + +```js +auth: { + strategies: { + yourFavoriteProvider: { + _scheme: 'oauth2', + scope: ['openid', 'profile', 'email'], + _runtimeOptions: () => { + return { + client_id: process.env.CLIENT_ID, + authorization_endpoint: process.env.AUTH_URL + '/auth', + access_token_endpoint: process.env.AUTH_URL + '/token', + userinfo_endpoint: process.env.AUTH_URL + '/userinfo' + } + } + } + } +} +``` +The `_runtimeOptions` function will be evaluated and merged with the other options during the initial request to the server. diff --git a/lib/core/utilities.js b/lib/core/utilities.js index 52e0430ad..16de76e30 100644 --- a/lib/core/utilities.js +++ b/lib/core/utilities.js @@ -6,6 +6,11 @@ export const isSameURL = (a, b) => a.split('?')[0] === b.split('?')[0] export const isRelativeURL = u => u && u.length && /^\/[a-zA-Z0-9@\-%_~][/a-zA-Z0-9@\-%_~]*[?]?([^#]*)#?([^#]*)$/.test(u) +const isSpaMode = ctx => process.client && !ctx.nuxtState + +// eslint-disable-next-line +const functionFromString = obj => Function('"use strict";return (' + obj + ')')() + export const parseQuery = queryString => { const query = {} const pairs = queryString.split('&') @@ -83,3 +88,33 @@ export function decodeValue (val) { // Return as is return val } + +export function handleRuntimeOptions (ctx, options) { + // _runtimeOptions is only supported in universal mode + if (isSpaMode(ctx)) { + if (options._runtimeOptions) { + // eslint-disable-next-line + console.error('[ERROR] [AUTH]: _runtimeOptions is only supported in universal mode.') + } + return {} + } + + const key = 'auth._runtimeOptions.' + options._name + + // Evaluate and share _runtimeOptions with client + if (process.server && options._runtimeOptions) { + const runtimeOptions = functionFromString(options._runtimeOptions)() + + ctx.beforeNuxtRender(({ nuxtState }) => { + nuxtState[key] = runtimeOptions + }) + return runtimeOptions + } + + // Fetch shared _runtimeOptions from client side + if (process.client && ctx.nuxtState && ctx.nuxtState[key]) { + return ctx.nuxtState[key] + } + + return {} +} diff --git a/lib/module/plugin.js b/lib/module/plugin.js index e1c4bdff5..4de09280b 100644 --- a/lib/module/plugin.js +++ b/lib/module/plugin.js @@ -15,8 +15,16 @@ export default function (ctx, inject) { // Register strategies <%= options.strategies.map(strategy => { + function getSchemeOptions(schemeOptions) { + // _runtimeOptions function must be stringified (not supported by JSON.stringify) + if (schemeOptions._runtimeOptions) { + schemeOptions._runtimeOptions = schemeOptions._runtimeOptions.toString() + } + return JSON.stringify(schemeOptions) + } + const scheme = 'scheme_' + hash(options.strategyScheme.get(strategy)) - const schemeOptions = JSON.stringify(strategy) + const schemeOptions = getSchemeOptions(strategy) const name = strategy._name return `// ${name}\n $auth.registerStrategy('${name}', new ${scheme}($auth, ${schemeOptions}))` }).join('\n\n ') diff --git a/lib/schemes/oauth2.js b/lib/schemes/oauth2.js index 9d8cc2d80..2af850b41 100644 --- a/lib/schemes/oauth2.js +++ b/lib/schemes/oauth2.js @@ -1,4 +1,4 @@ -import { encodeQuery, parseQuery } from '../utilities' +import { encodeQuery, parseQuery, handleRuntimeOptions } from '../utilities' import nanoid from 'nanoid' const isHttps = process.server ? require('is-https') : null @@ -14,7 +14,9 @@ export default class Oauth2Scheme { this.req = auth.ctx.req this.name = options._name - this.options = Object.assign({}, DEFAULTS, options) + const runtimeOptions = handleRuntimeOptions(auth.ctx, options) + + this.options = Object.assign({}, DEFAULTS, options, runtimeOptions) } get _scope () {