From 3e0666dc279bbe716e3bb236a051be31aad494c7 Mon Sep 17 00:00:00 2001 From: Aaron Granick Date: Tue, 27 Aug 2019 11:37:01 -0700 Subject: [PATCH] @okta/okta-vue: support PKCE flow (#537) * @okta/okta-vue: support PKCE flow - standardize on camel-case for configuration options. Using @okta/configuration-validation@0.4 for backward compatibility --- packages/okta-vue/README.md | 26 +++++----- packages/okta-vue/package.json | 4 +- packages/okta-vue/src/Auth.js | 14 +++--- .../e2e/harness/build/webpack.base.conf.js | 5 ++ .../okta-vue/test/e2e/harness/config/index.js | 2 +- .../okta-vue/test/e2e/harness/package.json | 1 + .../test/e2e/harness/src/router/index.js | 13 ++++- .../test/e2e/harness/test/e2e/specs/test.js | 48 +++++++++++++++++++ packages/okta-vue/test/e2e/harness/yarn.lock | 10 +++- packages/okta-vue/yarn.lock | 10 ++-- 10 files changed, 102 insertions(+), 31 deletions(-) diff --git a/packages/okta-vue/README.md b/packages/okta-vue/README.md index a8c8f2572..6b554c098 100644 --- a/packages/okta-vue/README.md +++ b/packages/okta-vue/README.md @@ -48,8 +48,8 @@ import Auth from '@okta/okta-vue' Vue.use(Auth, { issuer: 'https://{yourOktaDomain}.com/oauth2/default', - client_id: '{clientId}', - redirect_uri: 'http://localhost:{port}/implicit/callback', + clientId: '{clientId}', + redirectUri: 'http://localhost:{port}/implicit/callback', scope: 'openid profile email' }) @@ -212,21 +212,16 @@ router.beforeEach((to, from, next) { #### Configuration Options +he most commonly used options are shown here. See [Configuration Reference](https://github.com/okta/okta-auth-js#configuration-reference) for an extended set of supported options. + - `issuer` **(required)**: The OpenID Connect `issuer` -- `client_id` **(required)**: The OpenID Connect `client_id` -- `redirect_uri` **(required)**: Where the callback is hosted +- `clientId` **(required)**: The OpenID Connect `client_id` +- `redirectUri` **(required)**: Where the callback is hosted - `scope` *(optional)*: Reserved or custom claims to be returned in the tokens -- `response_type` *(optional)*: Desired token grant types -- `storage` *(optional)*: - Specify the type of storage for tokens. - The types are: - - [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) - - [`sessionStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) - - [`cookie`](https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie) - - Defaults to `localStorage`. If [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Local_storage) is not available, falls back to `sessionStorage` or `cookie`. -- `auto_renew` *(optional)*: - By default, the library will attempt to renew expired tokens. When an expired token is requested by the library, a renewal request is executed to update the token. If you wish to to disable auto renewal of tokens, set `auto_renew` to `false`. +- `responseType` *(optional)*: Desired token grant types. Default: `['id_token', 'token']`. For PKCE flow, this should be left undefined or set to `['code']`. +- `pkce` *(optional)* - If `true`, PKCE flow will be used +- `autoRenew` *(optional)*: + By default, the library will attempt to renew expired tokens. When an expired token is requested by the library, a renewal request is executed to update the token. If you wish to to disable auto renewal of tokens, set `autoRenew` to `false`. #### `$auth.loginRedirect(fromUri, additionalParams)` @@ -270,6 +265,7 @@ See the [getting started](/README.md#getting-started) section for step-by-step i | Command | Description | | -------------- | ---------------------------------- | +| `yarn install` | Install all dependencies | | `yarn start` | Start the sample app using the SDK | | `yarn test` | Run integration tests | | `yarn lint` | Run eslint linting tests | diff --git a/packages/okta-vue/package.json b/packages/okta-vue/package.json index c75a1dbdf..b97a6391d 100644 --- a/packages/okta-vue/package.json +++ b/packages/okta-vue/package.json @@ -47,8 +47,8 @@ }, "homepage": "https://github.com/okta/okta-oidc-js#readme", "dependencies": { - "@okta/configuration-validation": "^0.3.0", - "@okta/okta-auth-js": "^2.0.0", + "@okta/configuration-validation": "^0.4.0", + "@okta/okta-auth-js": "^2.6.3", "cross-env": "^5.1.1", "vue": "^2.5.17", "vue-router": "^3.0.1" diff --git a/packages/okta-vue/src/Auth.js b/packages/okta-vue/src/Auth.js index 444f6c9d9..ee96c3ed9 100644 --- a/packages/okta-vue/src/Auth.js +++ b/packages/okta-vue/src/Auth.js @@ -10,7 +10,7 @@ import ImplicitCallback from './components/ImplicitCallback' function install (Vue, options) { const authConfig = initConfig(options) - const oktaAuth = new AuthJS(buildConfigObject(authConfig)) + const oktaAuth = new AuthJS(authConfig) oktaAuth.userAgent = `${packageInfo.name}/${packageInfo.version} ${oktaAuth.userAgent}` Vue.prototype.$auth = { @@ -19,7 +19,7 @@ function install (Vue, options) { localStorage.setItem('referrerPath', fromUri) } return oktaAuth.token.getWithRedirect({ - responseType: authConfig.response_type, + responseType: authConfig.responseType, scopes: authConfig.scope.split(' '), ...additionalParams }) @@ -92,16 +92,18 @@ function install (Vue, options) { function handleCallback () { return ImplicitCallback } -const initConfig = auth => { +const initConfig = options => { + let auth = buildConfigObject(options) + // Assert configuration assertIssuer(auth.issuer, auth.testing) - assertClientId(auth.client_id) - assertRedirectUri(auth.redirect_uri) + assertClientId(auth.clientId) + assertRedirectUri(auth.redirectUri) if (!auth.scope) auth.scope = 'openid' // Use space separated response_type or default value - auth.response_type = (auth.response_type || 'id_token token').split(' ') + auth.responseType = (auth.responseType || 'id_token token').split(' ') return auth } diff --git a/packages/okta-vue/test/e2e/harness/build/webpack.base.conf.js b/packages/okta-vue/test/e2e/harness/build/webpack.base.conf.js index 9a5b185e0..378ee1c7b 100644 --- a/packages/okta-vue/test/e2e/harness/build/webpack.base.conf.js +++ b/packages/okta-vue/test/e2e/harness/build/webpack.base.conf.js @@ -46,6 +46,11 @@ module.exports = { loader: 'vue-loader', options: vueLoaderConfig }, + { + test: /\.js$/, + use: ['source-map-loader'], + enforce: 'pre' + }, { test: /\.js$/, loader: 'babel-loader', diff --git a/packages/okta-vue/test/e2e/harness/config/index.js b/packages/okta-vue/test/e2e/harness/config/index.js index 8e5430be8..f457f4b86 100644 --- a/packages/okta-vue/test/e2e/harness/config/index.js +++ b/packages/okta-vue/test/e2e/harness/config/index.js @@ -33,7 +33,7 @@ module.exports = { */ // https://webpack.js.org/configuration/devtool/#development - devtool: 'eval-source-map', + devtool: 'source-map', // If you have problems debugging vue-files in devtools, // set this to false - it *may* help diff --git a/packages/okta-vue/test/e2e/harness/package.json b/packages/okta-vue/test/e2e/harness/package.json index b9f13ac78..97bf654b0 100644 --- a/packages/okta-vue/test/e2e/harness/package.json +++ b/packages/okta-vue/test/e2e/harness/package.json @@ -59,6 +59,7 @@ "selenium-server": "^3.9.1", "semver": "^5.3.0", "shelljs": "^0.7.6", + "source-map-loader": "^0.2.4", "uglifyjs-webpack-plugin": "^1.1.1", "url-loader": "^0.5.8", "vue-loader": "13.0.2", diff --git a/packages/okta-vue/test/e2e/harness/src/router/index.js b/packages/okta-vue/test/e2e/harness/src/router/index.js index 1f91b4db1..2a7384352 100644 --- a/packages/okta-vue/test/e2e/harness/src/router/index.js +++ b/packages/okta-vue/test/e2e/harness/src/router/index.js @@ -5,10 +5,18 @@ import SessionTokenLogin from '@/components/SessionTokenLogin' import Auth from '@/../../../../dist/okta-vue.js' +// To perform end-to-end PKCE flow we must be configured on both ends: when the login is initiated, and on the callback +// The login page is loaded with a query param. This will select a unique callback url +// On the callback load we detect PKCE by inspecting the pathname +const url = new URL(window.location.href) +const pkce = !!url.searchParams.get('pkce') || url.pathname.indexOf('pkce/callback') >= 0 +const redirectUri = window.location.origin + (pkce ? '/pkce/callback' : '/implicit/callback') + let config = { issuer: process.env.ISSUER, - redirect_uri: process.env.REDIRECT_URI, - client_id: process.env.CLIENT_ID, + redirectUri, + pkce, + clientId: process.env.CLIENT_ID, scope: 'openid profile email', testing: { disableHttpsCheck: false @@ -29,6 +37,7 @@ const router = new Router({ base: '/', routes: [ { path: '/implicit/callback', component: Auth.handleCallback() }, + { path: '/pkce/callback', component: Auth.handleCallback() }, { path: '/protected', component: Protected, meta: { requiresAuth: true } }, { path: '/sessionToken', component: SessionTokenLogin } ] diff --git a/packages/okta-vue/test/e2e/harness/test/e2e/specs/test.js b/packages/okta-vue/test/e2e/harness/test/e2e/specs/test.js index 3cbd784db..8a3556bca 100644 --- a/packages/okta-vue/test/e2e/harness/test/e2e/specs/test.js +++ b/packages/okta-vue/test/e2e/harness/test/e2e/specs/test.js @@ -6,6 +6,54 @@ module.exports = { this.devServer = browser.globals.devServerURL }, + 'PKCE: redirects to Okta for login when trying to access a protected page': function (browser) { + // Login flow + browser + .url(this.devServer + '/protected?pkce=1') + .waitForElementVisible('body', 5000) + .waitForElementVisible('#okta-signin-username', 5000) + .waitForElementVisible('#okta-signin-password', 5000) + .waitForElementVisible('#okta-signin-submit', 1000) + .setValue('#okta-signin-username', process.env.USERNAME) + .setValue('#okta-signin-password', process.env.PASSWORD) + .click('#okta-signin-submit') + .waitForElementVisible('#app', 20000) + + // On the protected page + browser.expect.element('#logout-button').to.be.present.before(2000) + browser.expect.element('.protected').text.to.contain('Protected!').before(2000) + browser.expect.element('.protected .userinfo').text.to.contain('email').before(2000) + browser.assert.urlContains('/protected') + + /// Sign out + browser.click('#logout-button') + browser.expect.element('#login-button').to.be.present.before(2000) + browser.end() + }, + + 'PKCE: redirects to Okta for login': function (browser) { + // Login flow + browser + .url(this.devServer + '?pkce=1') + .waitForElementVisible('#app', 5000) + .click('#login-button') + .waitForElementVisible('#okta-signin-username', 5000) + .waitForElementVisible('#okta-signin-password', 5000) + .waitForElementVisible('#okta-signin-submit', 1000) + .setValue('#okta-signin-username', process.env.USERNAME) + .setValue('#okta-signin-password', process.env.PASSWORD) + .click('#okta-signin-submit') + .waitForElementVisible('#app', 20000) + + // Verify we are logged in + browser.expect.element('#logout-button').to.be.present.before(2000) + + /// Sign out + browser.click('#logout-button') + browser.expect.element('#login-button').to.be.present.before(2000) + browser.end() + }, + 'redirects to Okta for login when trying to access a protected page': function (browser) { // Login flow browser diff --git a/packages/okta-vue/test/e2e/harness/yarn.lock b/packages/okta-vue/test/e2e/harness/yarn.lock index 7b7d85353..1cf5f84de 100644 --- a/packages/okta-vue/test/e2e/harness/yarn.lock +++ b/packages/okta-vue/test/e2e/harness/yarn.lock @@ -266,7 +266,7 @@ async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.1.2, async@^2.4.1: +async@^2.1.2, async@^2.4.1, async@^2.5.0: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" dependencies: @@ -6306,6 +6306,14 @@ source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" +source-map-loader@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.4.tgz#c18b0dc6e23bf66f6792437557c569a11e072271" + integrity sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ== + dependencies: + async "^2.5.0" + loader-utils "^1.1.0" + source-map-resolve@^0.5.0: version "0.5.2" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" diff --git a/packages/okta-vue/yarn.lock b/packages/okta-vue/yarn.lock index de89e7693..188c20638 100644 --- a/packages/okta-vue/yarn.lock +++ b/packages/okta-vue/yarn.lock @@ -16,13 +16,15 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@okta/configuration-validation@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@okta/configuration-validation/-/configuration-validation-0.3.0.tgz#41954ce0567b25b7ff0d4eb7fc60468a709444d7" +"@okta/configuration-validation@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@okta/configuration-validation/-/configuration-validation-0.4.0.tgz#797cb7cabd61d792f8fb1cc42d9eed2115dc3fd9" + integrity sha512-gEtGld0eKP+Ikl7dWju/cU9bCtrwTCYD+glO9RchnxDFAn39WWT5+FaIGF3E+ZXLhyChfrUH2SZp0uaddeqsEQ== -"@okta/okta-auth-js@^2.0.0": +"@okta/okta-auth-js@^2.6.3": version "2.7.0" resolved "https://registry.yarnpkg.com/@okta/okta-auth-js/-/okta-auth-js-2.7.0.tgz#a3b7845ed77e129f424aebed464d3db88f82dfc1" + integrity sha512-WU8ZlpoDll5UqxxJ+gA+r0bJmNW+NzPM4+HBqEO/zTMLFgSSsOpQ/eXXGTq/1Sfa0xwOvd7wPBZGqgNkLmTs5A== dependencies: Base64 "0.3.0" cross-fetch "^3.0.0"