From f60a58f7ccb2450079fe43eb4b72aa488cdb9d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Tue, 18 Jun 2019 15:07:43 +0200 Subject: [PATCH 01/12] chore(pkg): Add is-https package --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index cef2d3cc7..5fa92e9c4 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "consola": "^2.7.1", "cookie": "^0.4.0", "dotprop": "^1.2.0", + "is-https": "^1.0.0", "js-cookie": "^2.2.0", "lodash": "^4.17.11", "nanoid": "^2.0.3" diff --git a/yarn.lock b/yarn.lock index b7bb72717..04020918c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5214,6 +5214,11 @@ is-glob@^4.0.0, is-glob@^4.0.1: dependencies: is-extglob "^2.1.1" +is-https@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-https/-/is-https-1.0.0.tgz#9c1dde000dc7e7288edb983bef379e498e7cb1bf" + integrity sha512-1adLLwZT9XEXjzhQhZxd75uxf0l+xI9uTSFaZeSESjL3E1eXSPpO+u5RcgqtzeZ1KCaNvtEwZSTO2P4U5erVqQ== + is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" From e6eda8439ee8d44f6067066226de547e3fe21d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Tue, 18 Jun 2019 15:08:01 +0200 Subject: [PATCH 02/12] chore(plugin): inject auth after strategies --- lib/module/plugin.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/module/plugin.js b/lib/module/plugin.js index 4f7dd80b9..51ce68fb9 100644 --- a/lib/module/plugin.js +++ b/lib/module/plugin.js @@ -12,11 +12,7 @@ export default function (ctx, inject) { // Create a new Auth instance const $auth = new Auth(ctx, options) - // Inject it to nuxt context as $auth - inject('auth', $auth) - // Register strategies - <%= options.strategies.map(strategy => { const scheme = 'scheme_' + hash(options.strategyScheme.get(strategy)) @@ -26,6 +22,9 @@ export default function (ctx, inject) { }).join('\n\n ') %> + // Inject it to nuxt context as $auth + inject('auth', $auth) + // Initialize auth return $auth.init().catch(error => { if (process.client) { From d580649ae0e7ed585894c4893220c06e910b3afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Tue, 18 Jun 2019 15:08:25 +0200 Subject: [PATCH 03/12] feat(oauth2): Handle server-side --- lib/schemes/oauth2.js | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/schemes/oauth2.js b/lib/schemes/oauth2.js index 387fc2cbd..86dff082b 100644 --- a/lib/schemes/oauth2.js +++ b/lib/schemes/oauth2.js @@ -1,5 +1,6 @@ import { encodeQuery, parseQuery } from '../utilities' import nanoid from 'nanoid' +const isHttps = process.server ? require('is-https') : null const DEFAULTS = { token_type: 'Bearer', @@ -10,6 +11,7 @@ const DEFAULTS = { export default class Oauth2Scheme { constructor (auth, options) { this.$auth = auth + this.req = auth.ctx.req this.name = options._name this.options = Object.assign({}, DEFAULTS, options) @@ -28,6 +30,12 @@ export default class Oauth2Scheme { return url } + if (process.server && this.req) { + const protocol = 'http' + (isHttps(this.req) ? 's' : '') + '://' + + return protocol + this.req.headers.host + this.$auth.options.redirect.callback + } + if (process.client) { return window.location.origin + this.$auth.options.redirect.callback } @@ -91,7 +99,7 @@ export default class Oauth2Scheme { opts.nonce = nonce || nanoid() } - this.$auth.$storage.setLocalStorage(this.name + '.state', opts.state) + this.$auth.$storage.setUniversal(this.name + '.state', opts.state) const url = this.options.authorization_endpoint + '?' + encodeQuery(opts) @@ -116,19 +124,18 @@ export default class Oauth2Scheme { } async _handleCallback (uri) { - // Callback flow is not supported in server side - if (process.server) { + // Handle callback only for specified route + if (this.$auth.options.redirect && this.$auth.ctx.route.path !== this.$auth.options.redirect.callback) { + return + } + // Callback flow is not supported in static generation + if (process.server && process.static) { return } - // Parse query from both search and hash fragments - const hash = parseQuery(window.location.hash.substr(1)) - const search = parseQuery(window.location.search.substr(1)) - const parsedQuery = Object.assign({}, search, hash) - + const parsedQuery = Object.assign({}, this.$auth.ctx.query, this.$auth.ctx.hash) // accessToken/idToken let token = parsedQuery[this.options.token_key || 'access_token'] - // refresh token let refreshToken = parsedQuery[this.options.refresh_token_key || 'refresh_token'] @@ -137,7 +144,7 @@ export default class Oauth2Scheme { const data = await this.$auth.request({ method: 'post', url: this.options.access_token_endpoint, - baseURL: false, + baseURL: process.server ? undefined : false, data: encodeQuery({ code: parsedQuery.code, client_id: this.options.client_id, @@ -162,8 +169,8 @@ export default class Oauth2Scheme { } // Validate state - const state = this.$auth.$storage.getLocalStorage(this.name + '.state') - this.$auth.$storage.setLocalStorage(this.name + '.state', null) + const state = this.$auth.$storage.getUniversal(this.name + '.state') + this.$auth.$storage.setUniversal(this.name + '.state', null) if (state && parsedQuery.state !== state) { return } From 2662c1e1f860c0859dbba549d09e7e91e732e614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Tue, 18 Jun 2019 15:08:56 +0200 Subject: [PATCH 04/12] hotfix(oauth2): No need to get parseQuery anymore --- lib/schemes/oauth2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/schemes/oauth2.js b/lib/schemes/oauth2.js index 86dff082b..214065e54 100644 --- a/lib/schemes/oauth2.js +++ b/lib/schemes/oauth2.js @@ -1,4 +1,4 @@ -import { encodeQuery, parseQuery } from '../utilities' +import { encodeQuery } from '../utilities' import nanoid from 'nanoid' const isHttps = process.server ? require('is-https') : null From 4f92d5a798455a70fd4095e1b927679d380cbbf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Tue, 18 Jun 2019 17:59:18 +0200 Subject: [PATCH 05/12] hotifx: remove baseURL since it's not needed anymore --- lib/schemes/oauth2.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/schemes/oauth2.js b/lib/schemes/oauth2.js index 214065e54..c6d981817 100644 --- a/lib/schemes/oauth2.js +++ b/lib/schemes/oauth2.js @@ -144,7 +144,6 @@ export default class Oauth2Scheme { const data = await this.$auth.request({ method: 'post', url: this.options.access_token_endpoint, - baseURL: process.server ? undefined : false, data: encodeQuery({ code: parsedQuery.code, client_id: this.options.client_id, From bb9ab030e0c4aedcac665acc8eaf331c67ac7ee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Tue, 18 Jun 2019 18:12:49 +0200 Subject: [PATCH 06/12] hotfix: Avoid regressions --- lib/schemes/oauth2.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/schemes/oauth2.js b/lib/schemes/oauth2.js index c6d981817..214065e54 100644 --- a/lib/schemes/oauth2.js +++ b/lib/schemes/oauth2.js @@ -144,6 +144,7 @@ export default class Oauth2Scheme { const data = await this.$auth.request({ method: 'post', url: this.options.access_token_endpoint, + baseURL: process.server ? undefined : false, data: encodeQuery({ code: parsedQuery.code, client_id: this.options.client_id, From 5153631da7369d7bb79a35aa234416ba3a0c8db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Tue, 18 Jun 2019 18:13:26 +0200 Subject: [PATCH 07/12] fix: use ctx.route --- lib/schemes/oauth2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/schemes/oauth2.js b/lib/schemes/oauth2.js index 214065e54..6cac00eb9 100644 --- a/lib/schemes/oauth2.js +++ b/lib/schemes/oauth2.js @@ -133,7 +133,7 @@ export default class Oauth2Scheme { return } - const parsedQuery = Object.assign({}, this.$auth.ctx.query, this.$auth.ctx.hash) + const parsedQuery = Object.assign({}, this.$auth.ctx.route.query, this.$auth.ctx.route.hash) // accessToken/idToken let token = parsedQuery[this.options.token_key || 'access_token'] // refresh token From 93860c0469f6234016d6da479bd01b1afb7590bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Fri, 21 Jun 2019 03:44:42 +0200 Subject: [PATCH 08/12] feat: add transform_credentials_endpoint option --- lib/schemes/oauth2.js | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/schemes/oauth2.js b/lib/schemes/oauth2.js index 6cac00eb9..d45c2b7da 100644 --- a/lib/schemes/oauth2.js +++ b/lib/schemes/oauth2.js @@ -5,7 +5,8 @@ const isHttps = process.server ? require('is-https') : null const DEFAULTS = { token_type: 'Bearer', response_type: 'token', - tokenName: 'Authorization' + tokenName: 'Authorization', + transform_credentials_endpoint: null } export default class Oauth2Scheme { @@ -139,9 +140,16 @@ export default class Oauth2Scheme { // refresh token let refreshToken = parsedQuery[this.options.refresh_token_key || 'refresh_token'] + // Validate state + const state = this.$auth.$storage.getUniversal(this.name + '.state') + this.$auth.$storage.setUniversal(this.name + '.state', null) + if (state && parsedQuery.state !== state) { + return + } + // -- Authorization Code Grant -- if (this.options.response_type === 'code' && parsedQuery.code) { - const data = await this.$auth.request({ + let data = await this.$auth.request({ method: 'post', url: this.options.access_token_endpoint, baseURL: process.server ? undefined : false, @@ -155,6 +163,16 @@ export default class Oauth2Scheme { }) }) + // Transform credentials (SSR Oauth on API side) + if (this.options.transform_credentials_endpoint) { + data = await this.$auth.request({ + method: 'post', + url: this.options.transform_credentials_endpoint, + data + }) + } + + if (data.access_token) { token = data.access_token } @@ -168,13 +186,6 @@ export default class Oauth2Scheme { return } - // Validate state - const state = this.$auth.$storage.getUniversal(this.name + '.state') - this.$auth.$storage.setUniversal(this.name + '.state', null) - if (state && parsedQuery.state !== state) { - return - } - // Append token_type if (this.options.token_type) { token = this.options.token_type + ' ' + token From 3cc9f17995777dd429d474ec24f32cfb47589d71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Fri, 21 Jun 2019 03:47:48 +0200 Subject: [PATCH 09/12] hotfix: lint issue --- lib/schemes/oauth2.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/schemes/oauth2.js b/lib/schemes/oauth2.js index d45c2b7da..a0beb69f7 100644 --- a/lib/schemes/oauth2.js +++ b/lib/schemes/oauth2.js @@ -172,7 +172,6 @@ export default class Oauth2Scheme { }) } - if (data.access_token) { token = data.access_token } From 265f20c78400d3ee38b0f5372f87503d3e64b2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Fri, 21 Jun 2019 14:31:52 +0200 Subject: [PATCH 10/12] feat: expose in the context --- lib/module/plugin.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/module/plugin.js b/lib/module/plugin.js index 51ce68fb9..e1c4bdff5 100644 --- a/lib/module/plugin.js +++ b/lib/module/plugin.js @@ -24,6 +24,7 @@ export default function (ctx, inject) { // Inject it to nuxt context as $auth inject('auth', $auth) + ctx.$auth = $auth // Initialize auth return $auth.init().catch(error => { From d4e1a17269172841890f7ee2a06fb68c20efad93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Fri, 21 Jun 2019 15:06:24 +0200 Subject: [PATCH 11/12] fix: revert transform_credentials_endpoint --- lib/schemes/oauth2.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/schemes/oauth2.js b/lib/schemes/oauth2.js index a0beb69f7..389c5d0e0 100644 --- a/lib/schemes/oauth2.js +++ b/lib/schemes/oauth2.js @@ -5,8 +5,7 @@ const isHttps = process.server ? require('is-https') : null const DEFAULTS = { token_type: 'Bearer', response_type: 'token', - tokenName: 'Authorization', - transform_credentials_endpoint: null + tokenName: 'Authorization' } export default class Oauth2Scheme { @@ -163,15 +162,6 @@ export default class Oauth2Scheme { }) }) - // Transform credentials (SSR Oauth on API side) - if (this.options.transform_credentials_endpoint) { - data = await this.$auth.request({ - method: 'post', - url: this.options.transform_credentials_endpoint, - data - }) - } - if (data.access_token) { token = data.access_token } From bcc399b7efb0d0740abff4ccb4db0f858f61b1dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Fri, 21 Jun 2019 15:11:50 +0200 Subject: [PATCH 12/12] chore(docs): Updating docs to reflect context.$auth --- docs/api/auth.md | 10 +++++----- docs/recipes/extend.md | 6 +++--- lib/core/middleware.js | 8 ++++---- test/fixtures/basic/plugins/auth.js | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/api/auth.md b/docs/api/auth.md index fd5998a20..591eb1f32 100644 --- a/docs/api/auth.md +++ b/docs/api/auth.md @@ -3,7 +3,7 @@ [Source Code](https://github.com/nuxt-community/auth-module/blob/dev/lib/core/auth.js) This module globally injects `$auth` instance, meaning that you can access it anywhere using `this.$auth`. -For plugins, asyncData, fetch, nuxtServerInit and Middleware, you can access it from `context.app.$auth`. +For plugins, asyncData, fetch, nuxtServerInit and Middleware, you can access it from `context.$auth`. ## properties @@ -118,8 +118,8 @@ this.$auth.setToken('local', '.....') Listen for auth errors: (`plugins/auth.js`) ```js -export default function({ app }) { - app.$auth.onError((error, name, endpoint) => { +export default function({ $auth }) { + $auth.onError((error, name, endpoint) => { console.error(name, error) }) } @@ -130,8 +130,8 @@ export default function({ app }) { Pre-process URLs before redirect: (`plugins/auth.js`) ```js -export default function({ app }) { - app.$auth.onRedirect((to, from) => { +export default function({ $auth }) { + $auth.onRedirect((to, from) => { console.error(to) // you can optionally change `to` by returning a new value }) diff --git a/docs/recipes/extend.md b/docs/recipes/extend.md index 19b529f7f..7f27d453a 100644 --- a/docs/recipes/extend.md +++ b/docs/recipes/extend.md @@ -18,11 +18,11 @@ If you have plugins that need to access `$auth`, you can use `auth.plugins` opti `plugins/auth.js` ```js -export default function ({ app }) { - if (!app.$auth.loggedIn) { +export default function ({ $auth }) { + if (!$auth.loggedIn) { return } - const username = app.$auth.user.username + const username = $auth.user.username } ``` diff --git a/lib/core/middleware.js b/lib/core/middleware.js index 68836006e..9d5c0a201 100644 --- a/lib/core/middleware.js +++ b/lib/core/middleware.js @@ -14,16 +14,16 @@ Middleware.auth = function (ctx) { return } - const { login, callback } = ctx.app.$auth.options.redirect + const { login, callback } = ctx.$auth.options.redirect - if (ctx.app.$auth.$state.loggedIn) { + if (ctx.$auth.$state.loggedIn) { // -- Authorized -- // Redirect to home page if: // - inside login page // - login page disabled // - options: { auth: 'guest' } is set on the page if (!login || normalizePath(ctx.route.path) === normalizePath(login) || routeOption(ctx.route, 'auth', 'guest')) { - ctx.app.$auth.redirect('home') + ctx.$auth.redirect('home') } } else { // -- Guest -- @@ -31,7 +31,7 @@ Middleware.auth = function (ctx) { // (Those passing `callback` at runtime need to mark their callback component // with `auth: false` to avoid an unnecessary redirect from callback to login) if (!callback || normalizePath(ctx.route.path) !== normalizePath(callback)) { - ctx.app.$auth.redirect('login') + ctx.$auth.redirect('login') } } } diff --git a/test/fixtures/basic/plugins/auth.js b/test/fixtures/basic/plugins/auth.js index 6cdfbeac6..5887464f4 100644 --- a/test/fixtures/basic/plugins/auth.js +++ b/test/fixtures/basic/plugins/auth.js @@ -1,3 +1,3 @@ -export default function ({ app }) { - app.$auth._custom_plugin = true +export default function ({ $auth }) { + $auth._custom_plugin = true }