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
7 changes: 3 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,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) {
Expand Down
58 changes: 38 additions & 20 deletions lib/schemes/oauth2.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
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',
response_type: 'token',
tokenName: 'Authorization'
tokenName: 'Authorization',
transform_credentials_endpoint: null
}

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 +31,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 +100,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 +125,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 @@ -148,6 +163,16 @@ export default class Oauth2Scheme {
})
})

// Transform credentials (SSR Oauth on API side)
if (this.options.transform_credentials_endpoint) {
atinux marked this conversation as resolved.
Show resolved Hide resolved
data = await this.$auth.request({
method: 'post',
url: this.options.transform_credentials_endpoint,
data
})
}


if (data.access_token) {
token = data.access_token
}
Expand All @@ -161,13 +186,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
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