Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(oauth2): support server-side callback #381

Merged
merged 13 commits into from
Jun 23, 2019
10 changes: 5 additions & 5 deletions docs/api/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
})
}
Expand All @@ -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
})
Expand Down
6 changes: 3 additions & 3 deletions docs/recipes/extend.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
```
8 changes: 4 additions & 4 deletions lib/core/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,24 @@ 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 --
// Redirect to login page if not authorized and not inside callback page
// (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')
}
}
}
8 changes: 4 additions & 4 deletions lib/module/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -26,6 +22,10 @@ export default function (ctx, inject) {
}).join('\n\n ')
%>

// Inject it to nuxt context as $auth
inject('auth', $auth)
ctx.$auth = $auth

// Initialize auth
return $auth.init().catch(error => {
if (process.client) {
Expand Down
45 changes: 26 additions & 19 deletions lib/schemes/oauth2.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { encodeQuery, parseQuery } from '../utilities'
import { encodeQuery } from '../utilities'
import nanoid from 'nanoid'
const isHttps = process.server ? require('is-https') : null

const DEFAULTS = {
token_type: 'Bearer',
Expand All @@ -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)
Expand All @@ -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
}
Expand Down Expand Up @@ -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)

Expand All @@ -116,28 +124,34 @@ 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.route.query, this.$auth.ctx.route.hash)
// accessToken/idToken
let token = parsedQuery[this.options.token_key || 'access_token']

// refresh token
let refreshToken = parsedQuery[this.options.refresh_token_key || 'refresh_token']

// Validate state
atinux marked this conversation as resolved.
Show resolved Hide resolved
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: false,
baseURL: process.server ? undefined : false,
data: encodeQuery({
code: parsedQuery.code,
client_id: this.options.client_id,
Expand All @@ -161,13 +175,6 @@ export default class Oauth2Scheme {
return
}

// Validate state
const state = this.$auth.$storage.getLocalStorage(this.name + '.state')
this.$auth.$storage.setLocalStorage(this.name + '.state', null)
if (state && parsedQuery.state !== state) {
return
}

// Append token_type
if (this.options.token_type) {
token = this.options.token_type + ' ' + token
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/basic/plugins/auth.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export default function ({ app }) {
app.$auth._custom_plugin = true
export default function ({ $auth }) {
$auth._custom_plugin = true
}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down