Skip to content

Commit

Permalink
updates Vue SDK to authJS 3.x (#728)
Browse files Browse the repository at this point in the history
* updates Vue SDK to authJS 3.x
  • Loading branch information
swiftone authored Apr 2, 2020
1 parent 2610c8b commit bfa06cf
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 103 deletions.
13 changes: 13 additions & 0 deletions packages/okta-vue/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# 2.0.0

### Breaking Changes

- Uses/requires `@okta/okta-auth-js 3.x`
- The `pkce` option now defaults to `true`, using the Authorization Code w/PKCE flow
- Those using the (previous default) Implicit Flow should pass `pkce: false` to their config
- See the [@okta/okta-auth-js README regarding PKCE OAuth2 Flow](https://github.com/okta/okta-auth-js#pkce-oauth-20-flow) for PKCE requirements
- Which include the Application settings in the Okta Admin Dashboard allowing for PKCE
- The previously deprecated `scope` option is now fully unsupported
- The `scopes` option now defaults to `['openid', 'email', 'profile']` instead of the previous `['openid']`
- This default continues to be overridden by any explicit `scopes` passed in the config

# 1.3.0

### Features
Expand Down
8 changes: 2 additions & 6 deletions packages/okta-vue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,22 +236,18 @@ The most commonly used options are shown here. See [Configuration Reference](htt
- `clientId` **(required)**: The OpenID Connect `client_id`
- `redirectUri` **(required)**: Where the callback is hosted
- `postLogoutRedirectUri` | Specify the url where the browser should be redirected after [logout](#authlogouturi). This url must be added to the list of `Logout redirect URIs` on the application's `General Settings` tab.
- `scope` *(deprecated in v1.1.1)*: Use `scopes` instead
- `scopes` *(optional)*: Reserved or custom claims to be returned in the tokens. Defaults to `openid`, which will only return the `sub` claim. To obtain more information about the user, use `openid profile`. For a list of scopes and claims, please see [Scope-dependent claims](https://developer.okta.com/standards/OIDC/index.html#scope-dependent-claims-not-always-returned) for more information.
- `scopes` *(optional)*: Reserved or custom claims to be returned in the tokens. Defaults to `['openid', 'email', 'profile']`. For a list of scopes and claims, please see [Scope-dependent claims](https://developer.okta.com/standards/OIDC/index.html#scope-dependent-claims-not-always-returned) for more information.
- `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
- `pkce` *(optional)* - If `true`, Authorization Code w/PKCE flow will be used. Defaults to `true`
- `onAuthRequired` *(optional)*: - callback function. Called when authentication is required. If not supplied, `okta-vue` will redirect directly to Okta for authentication. This is triggered when:
1. [login](#authloginfromuri-additionalparams) is called
2. A route protected by `$auth.authRedirectGuard` is accessed without authentication
- `onSessionExpired` *(optional)* - callback function. Called when the Okta SSO session has expired or was ended outside of the application. This SDK adds a default handler which will call [login](#authloginfromuri-additionalparams) to initiate a login flow. Passing a function here will disable the default handler.
- `isAuthenticated` *(optional)* - callback function. By default, [$auth.isAuthenticated](#authisauthenticated) will return true if both `getIdToken()` and `getAccessToken()` return a value. Setting a `isAuthenticated` function on the config will skip the default logic and call the supplied function instead. The function should return a Promise and resolve to either true or false.
- `tokenManager` *(optional)*: An object containing additional properties used to configure the internal token manager. See [AuthJS TokenManager](https://github.com/okta/okta-auth-js#the-tokenmanager) for more detailed information.

- `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.

- `secure`: If `true` then only "secure" https cookies will be stored. This option will prevent cookies from being stored on an HTTP connection. This option is only relevant if `storage` is set to `cookie`, or if the client browser does not support `localStorage` or `sessionStorage`, in which case `cookie` storage will be used.

- `storage` *(optional)*:
Specify the type of storage for tokens.
The types are:
Expand Down
4 changes: 2 additions & 2 deletions packages/okta-vue/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@okta/okta-vue",
"version": "1.3.0",
"version": "2.0.0",
"description": "Vue support for Okta",
"main": "dist/okta-vue.js",
"files": [
Expand Down Expand Up @@ -52,7 +52,7 @@
"homepage": "https://github.com/okta/okta-oidc-js#readme",
"dependencies": {
"@okta/configuration-validation": "^0.4.1",
"@okta/okta-auth-js": "^2.11.2",
"@okta/okta-auth-js": "^3.0.1",
"cross-env": "^5.1.1",
"vue": "^2.5.17",
"vue-router": "^3.0.1"
Expand Down
26 changes: 13 additions & 13 deletions packages/okta-vue/src/Auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,14 @@ export default class Auth {
}

async handleAuthentication () {
const tokens = await this.oktaAuth.token.parseFromUrl()
tokens.forEach(token => {
if (token.accessToken) this.oktaAuth.tokenManager.add('accessToken', token)
if (token.idToken) this.oktaAuth.tokenManager.add('idToken', token)
})
const {tokens} = await this.oktaAuth.token.parseFromUrl()

if (tokens.idToken) {
this.oktaAuth.tokenManager.add('idToken', tokens.idToken)
}
if (tokens.accessToken) {
this.oktaAuth.tokenManager.add('accessToken', tokens.accessToken)
}
}

setFromUri (fromUri) {
Expand Down Expand Up @@ -97,15 +100,12 @@ export default class Auth {
async getUser () {
const accessToken = await this.oktaAuth.tokenManager.get('accessToken')
const idToken = await this.oktaAuth.tokenManager.get('idToken')
if (accessToken && idToken) {
const userinfo = await this.oktaAuth.token.getUserInfo(accessToken)
if (userinfo.sub === idToken.claims.sub) {
// Only return the userinfo response if subjects match to
// mitigate token substitution attacks
return userinfo
}

if (!accessToken || !idToken) {
return idToken ? idToken.claims : undefined
}
return idToken ? idToken.claims : undefined

return this.oktaAuth.token.getUserInfo()
}

authRedirectGuard () {
Expand Down
7 changes: 4 additions & 3 deletions packages/okta-vue/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ export default function initConfig (options) {
assertClientId(auth.clientId)
assertRedirectUri(auth.redirectUri)

// Ensure "openid" exists in the scopes
auth.scopes = auth.scopes || []
if (auth.scopes.indexOf('openid') < 0) {
// Default scopes, override as needed
auth.scopes = auth.scopes || ['openid', 'email', 'profile']
// Force 'openid' as a scope
if (!auth.scopes.includes('openid')) {
auth.scopes.unshift('openid')
}

Expand Down
88 changes: 22 additions & 66 deletions packages/okta-vue/test/jest/Auth.interface.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('Auth constructor', () => {
})
})

test('sets the right user agent on AuthJS', () => {
it('sets the right user agent on AuthJS', () => {
const expectedUserAgent = `${pkg.name}/${pkg.version} foo`
createAuth()
expect(mockAuthJsInstance.userAgent).toMatch(expectedUserAgent)
Expand All @@ -50,36 +50,18 @@ describe('Auth constructor', () => {
it('sets the right scope and response_type when constructing AuthJS instance', () => {
createAuth()
expect(AuthJS).toHaveBeenCalledWith(Object.assign({}, baseConfig, {
scopes: ['openid'],
scopes: ['openid', 'email', 'profile'],
responseType: ['id_token', 'token'],
onSessionExpired: expect.any(Function)
}))
})

test('sets the right scope and response_type overrides (legacy config)', async () => {
const legacyConfig = {
issuer: 'https://foo',
client_id: 'foo',
redirect_uri: 'foo',
scope: 'foo bar',
response_type: 'token foo'
}
createAuth(legacyConfig)
expect(AuthJS).toHaveBeenCalledWith(Object.assign({}, legacyConfig, {
clientId: 'foo',
redirectUri: 'foo',
scopes: ['openid', 'foo', 'bar'],
responseType: ['token', 'foo'],
onSessionExpired: expect.any(Function)
}))
})

it('will not overwrite responseType if set', () => {
createAuth(extendConfig({
responseType: ['fake']
}))
expect(AuthJS).toHaveBeenCalledWith(Object.assign({}, baseConfig, {
scopes: ['openid'],
scopes: ['openid', 'email', 'profile'],
responseType: ['fake'],
onSessionExpired: expect.any(Function)
}))
Expand Down Expand Up @@ -246,25 +228,25 @@ describe('logout', () => {
auth = createAuth()
})

test('calls "signOut', async () => {
it('calls "signOut', async () => {
await auth.logout()
expect(mockAuthJsInstance.signOut).toHaveBeenCalled()
})

test('passes options', async () => {
it('passes options', async () => {
const options = { foo: 'bar' }
await auth.logout(options)
expect(mockAuthJsInstance.signOut).toHaveBeenCalledWith(options)
})

test('returns a promise', async () => {
it('returns a promise', async () => {
const res = auth.logout()
expect(typeof res.then).toBe('function')
expect(typeof res.catch).toBe('function')
return res
})

test('can throw', async () => {
it('can throw', async () => {
const testError = new Error('test error')
signoutRes = Promise.reject(testError)
return auth.logout()
Expand All @@ -285,7 +267,7 @@ describe('isAuthenticated', () => {
})
auth = createAuth(extendConfig(config))
}
test('isAuthenticated() returns false when the TokenManager throws an error', async () => {
it('isAuthenticated() returns false when the TokenManager throws an error', async () => {
bootstrap()
mockAuthJsInstance.tokenManager = {
get: jest.fn().mockImplementation(() => {
Expand All @@ -297,7 +279,7 @@ describe('isAuthenticated', () => {
expect(authenticated).toBeFalsy()
})

test('isAuthenticated() returns false when the TokenManager does not return an access token', async () => {
it('isAuthenticated() returns false when the TokenManager does not return an access token', async () => {
bootstrap()
mockAuthJsInstance.tokenManager = {
get: jest.fn().mockImplementation(() => {
Expand All @@ -308,7 +290,7 @@ describe('isAuthenticated', () => {
expect(authenticated).toBeFalsy()
})

test('isAuthenticated() returns true when the TokenManager returns an access token', async () => {
it('isAuthenticated() returns true when the TokenManager returns an access token', async () => {
bootstrap()
mockAuthJsInstance.tokenManager = {
get: jest.fn().mockReturnValue(Promise.resolve({ accessToken: 'fake' }))
Expand Down Expand Up @@ -338,7 +320,7 @@ describe('handleAuthentication', () => {
function bootstrap (tokens) {
mockAuthJsInstance = extendMockAuthJS({
token: {
parseFromUrl: jest.fn().mockReturnValue(Promise.resolve(tokens))
parseFromUrl: jest.fn().mockReturnValue(Promise.resolve({ tokens }))
},
tokenManager: {
add: jest.fn()
Expand All @@ -353,18 +335,18 @@ describe('handleAuthentication', () => {
it('stores accessToken and idToken', async () => {
var accessToken = { accessToken: 'X' }
var idToken = { idToken: 'Y' }
bootstrap([
bootstrap({
accessToken,
idToken
])
})
await auth.handleAuthentication()
expect(mockAuthJsInstance.tokenManager.add).toHaveBeenNthCalledWith(1, 'accessToken', accessToken)
expect(mockAuthJsInstance.tokenManager.add).toHaveBeenNthCalledWith(2, 'idToken', idToken)
expect(mockAuthJsInstance.tokenManager.add).toHaveBeenNthCalledWith(1, 'idToken', idToken)
expect(mockAuthJsInstance.tokenManager.add).toHaveBeenNthCalledWith(2, 'accessToken', accessToken)
})
})

describe('setFromUri', () => {
test('sets referrer in localStorage', () => {
it('sets referrer in localStorage', () => {
const TEST_VALUE = 'foo-bar'
localStorage.setItem('referrerPath', '')
const auth = createAuth()
Expand All @@ -374,7 +356,7 @@ describe('setFromUri', () => {
})

describe('getFromUri', () => {
test('cleares referrer from localStorage', () => {
it('cleares referrer from localStorage', () => {
const TEST_VALUE = 'foo-bar'
localStorage.setItem('referrerPath', TEST_VALUE)
const auth = createAuth()
Expand All @@ -399,7 +381,7 @@ describe('getAccessToken', () => {
auth = createAuth()
}

test('can retrieve an accessToken from the tokenManager', async () => {
it('can retrieve an accessToken from the tokenManager', async () => {
const accessToken = { accessToken: 'fake' }
bootstrap(accessToken)
const val = await auth.getAccessToken()
Expand All @@ -423,7 +405,7 @@ describe('getIdToken', () => {
auth = createAuth()
}

test('can retrieve an idToken from the tokenManager', async () => {
it('can retrieve an idToken from the tokenManager', async () => {
const idToken = { idToken: 'fake' }
bootstrap(idToken)
const val = await auth.getIdToken()
Expand Down Expand Up @@ -456,13 +438,13 @@ describe('getUser', () => {
auth = createAuth()
}

test('no tokens: returns undefined', async () => {
it('no tokens: returns undefined', async () => {
bootstrap()
const val = await auth.getUser()
expect(val).toBe(undefined)
})

test('idToken only: returns claims', async () => {
it('idToken only: returns claims', async () => {
const claims = { foo: 'bar' }
bootstrap({
idToken: { claims }
Expand All @@ -471,7 +453,7 @@ describe('getUser', () => {
expect(val).toBe(claims)
})

test('idToken and accessToken: calls getUserInfo', async () => {
it('idToken and accessToken: calls getUserInfo', async () => {
bootstrap({
accessToken: {},
idToken: { claims: {} },
Expand All @@ -480,32 +462,6 @@ describe('getUser', () => {
await auth.getUser()
expect(mockAuthJsInstance.token.getUserInfo).toHaveBeenCalled()
})

test('idToken and accessToken: matching sub returns userInfo', async () => {
const sub = 'fake'
const userInfo = { sub }
const claims = { sub }
bootstrap({
accessToken: {},
idToken: { claims },
userInfo
})
const val = await auth.getUser()
expect(val).toBe(userInfo)
})

test('idToken and accessToken: mis-matching sub returns claims', async () => {
const sub = 'fake'
const userInfo = { sub: 'not-fake?' }
const claims = { sub }
bootstrap({
accessToken: {},
idToken: { claims },
userInfo
})
const val = await auth.getUser()
expect(val).toBe(claims)
})
})

describe('TokenManager', () => {
Expand Down
17 changes: 4 additions & 13 deletions packages/okta-vue/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,15 @@
version "0.4.1"
resolved "https://registry.yarnpkg.com/@okta/configuration-validation/-/configuration-validation-0.4.1.tgz#6fa4520bc96c27b3d7aedcb0523de1fbceee9105"

"@okta/okta-auth-js@^2.11.2":
version "2.11.2"
resolved "https://registry.yarnpkg.com/@okta/okta-auth-js/-/okta-auth-js-2.11.2.tgz#d2d867e45eb98d453f156ec68c927bf1594277f6"
"@okta/okta-auth-js@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@okta/okta-auth-js/-/okta-auth-js-3.0.1.tgz#90ed51ec194845595156d8260414950ee071cba9"
integrity sha512-g36mvt5q9NScY1ljFHpahGzkTbWL0FzWM8vrNcJHMGk1ep7/5Y9eYqq5OF99QVLeJI3APsw8FneyXneCSeF7sw==
dependencies:
Base64 "0.3.0"
cross-fetch "^3.0.0"
js-cookie "2.2.0"
node-cache "^4.2.0"
q "1.4.1"
reqwest "2.0.5"
tiny-emitter "1.1.0"
xhr2 "0.1.3"

Expand Down Expand Up @@ -5128,10 +5127,6 @@ punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"

[email protected]:
version "1.4.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e"

qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
Expand Down Expand Up @@ -5402,10 +5397,6 @@ require-uncached@^1.0.2:
caller-path "^0.1.0"
resolve-from "^1.0.0"

[email protected]:
version "2.0.5"
resolved "https://registry.yarnpkg.com/reqwest/-/reqwest-2.0.5.tgz#00fb15ac4918c419ca82b43f24c78882e66039a1"

resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
Expand Down

0 comments on commit bfa06cf

Please sign in to comment.