Skip to content
This repository has been archived by the owner on Jan 26, 2025. It is now read-only.

📦 Prepping 1.0.0 vue #160

Merged
merged 3 commits into from
Mar 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 35 additions & 13 deletions packages/okta-vue/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
# Okta Vue SDK

The Okta Vue SDK is a wrapper around the [Okta Auth SDK](https://github.com/okta/okta-auth-js), which builds on top of Okta's [OpenID Connect API](https://developer.okta.com/docs/api/resources/oidc.html).

This library currently supports:
- [OAuth 2.0 Implicit Flow](https://tools.ietf.org/html/rfc6749#section-1.3.2)

- [OAuth 2.0 Implicit Flow](https://tools.ietf.org/html/rfc6749#section-1.3.2)

## Getting Started
* If you do not already have a **Developer Edition Account**, you can create one at [https://developer.okta.com/signup/](https://developer.okta.com/signup/).
* If you don't have a Vue app, or are new to Vue, please start with the [Vue CLI](https://github.com/vuejs/vue-cli) guide. It will walk you through the creation of a Vue app, creating [routers](https://router.vuejs.org/en/essentials/getting-started.html), and other application development essentials.

- If you do not already have a **Developer Edition Account**, you can create one at [https://developer.okta.com/signup/](https://developer.okta.com/signup/).
- If you don't have a Vue app, or are new to Vue, please start with the [Vue CLI](https://github.com/vuejs/vue-cli) guide. It will walk you through the creation of a Vue app, creating [routers](https://router.vuejs.org/en/essentials/getting-started.html), and other application development essentials.

### Add an OpenID Connect Client in Okta

In Okta, applications are OpenID Connect clients that can use Okta Authorization servers to authenticate users. Your Okta Org already has a default authorization server, so you just need to create an OIDC client that will use it.
* Log into the Okta Developer Dashboard, click **Applications** > **Add Application**.
* Choose **Single Page App (SPA)** as the platform, then submit the form the default values, which should look like this:

- Log into the Okta Developer Dashboard, click **Applications** > **Add Application**.
- Choose **Single Page App (SPA)** as the platform, then submit the form the default values, which should look like this:

| Setting | Value |
| ------------------- | ---------------------------------------------- |
Expand All @@ -27,7 +32,6 @@ After you have created the application there are two more values you will need t
| Client ID | In the applications list, or on the "General" tab of a specific application. |
| Org URL | On the home screen of the developer dashboard, in the upper right. |


These values will be used in your Vue application to setup the OpenID Connect flow with Okta.

## Installation
Expand All @@ -39,6 +43,7 @@ npm install --save @okta/okta-vue
```

### Configuration

You will need the values from the OIDC client that you created in the previous step to instantiate the middleware. You will also need to know your Okta Org URL, which you can see on the home page of the Okta Developer console.

In your application's [vue-router](https://router.vuejs.org/en/essentials/getting-started.html) configuration, import the `@okta/okta-vue` plugin and pass it your OpenID Connect client information:
Expand All @@ -58,6 +63,7 @@ Vue.use(Auth, {
```

### Use the Callback Handler

In order to handle the redirect back from Okta, you need to capture the token values from the URL. You'll use `/implicit/callback` as the callback URL, and use the default `Auth.handleCallback()` component included.

```typescript
Expand All @@ -74,6 +80,7 @@ const router = new Router({
```

### Add a Protected Route

Routes are protected by the `authRedirectGuard`, which verifies there is a valid `accessToken` or `idToken` stored. To ensure the user has been authenticated before accessing your route, add the `requiresAuth` metadata:

```typescript
Expand All @@ -99,6 +106,7 @@ router.beforeEach(Vue.prototype.$auth.authRedirectGuard())
If a user does not have a valid session, they will be redirected to the Okta Login Page for authentication. Once authenticated, they will be redirected back to your application's **protected** page.

### Show Login and Logout Buttons

In the relevant location in your application, you will want to provide `Login` and `Logout` buttons for the user. You can show/hide the correct button by using the `$auth.isAuthenticated()` method. For example:

```typescript
Expand Down Expand Up @@ -145,6 +153,7 @@ export default {
```

### Use the Access Token

When your users are authenticated, your Vue application has an access token that was issued by your Okta Authorization server. You can use this token to authenticate requests for resources on your server or API. As a hypothetical example, let's say you have an API that provides messages for a user. You could create a `MessageList` component that gets the access token and uses it to make an authenticated request to your server.

Here is what the Vue component could look like for this hypothentical example using [axios](https://github.com/axios/axios):
Expand Down Expand Up @@ -184,6 +193,7 @@ export default {
```

### Using a custom login-page

The `okta-vue` SDK supports the session token redirect flow for custom login pages. For more information, [see the basic Okta Sign-in Widget functionality](https://github.com/okta/okta-signin-widget#new-oktasigninconfig).

To handle the session-token redirect flow, you can create your own navigation guard using the `requiresAuth` meta param:
Expand All @@ -202,43 +212,55 @@ router.beforeEach((from, to, next) {
```

## Reference

### `$auth`

`$auth` is the top-most component of okta-vue. This is where most of the configuration is provided.

#### Configuration Options
- `issuer` **(required)**: The OpenID Connect `issuer`
- `client_id` **(required)**: The OpenID Connect `client_id`
- `redirect_uri` **(required)**: Where the callback is hosted
- `scope` *(optional)*: Reserved or custom claims to be returned in the tokens

#### `$auth.loginRedirect`
Performs a full page redirect to Okta based on the initial configuration. If you have an Okta `sessionToken`, you can bypass the full-page redirect by passing in this token. This is recommended when using the [Okta Sign-In Widget](https://github.com/okta/okta-signin-widget). Simply pass in a `sessionToken` into the `loginRedirect` method follows:
- `issuer` **(required)**: The OpenID Connect `issuer`
- `client_id` **(required)**: The OpenID Connect `client_id`
- `redirect_uri` **(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

#### `$auth.loginRedirect(fromUri, additionalParams)`

Performs a full page redirect to Okta based on the initial configuration. This method accepts a `fromUri` parameter to push the user to after successful authentication.

The parameter `additionalParams` is mapped to the [AuthJS OpenID Connect Options](https://github.com/okta/okta-auth-js#openid-connect-options). This will override any existing [configuration](#configuration). As an example, if you have an Okta `sessionToken`, you can bypass the full-page redirect by passing in this token. This is recommended when using the [Okta Sign-In Widget](https://github.com/okta/okta-signin-widget). Simply pass in a `sessionToken` into the `loginRedirect` method follows:

```typescript
this.$auth.loginRedirect({
this.$auth.loginRedirect('/profile', {
sessionToken: /* sessionToken */
})
```

> Note: For information on obtaining a `sessionToken` using the [Okta Sign-In Widget](https://github.com/okta/okta-signin-widget), please see the [`renderEl()` example](https://github.com/okta/okta-signin-widget#rendereloptions-success-error).

#### `$auth.isAuthenticated`

Returns `true` if there is a valid access token or ID token.

#### `$auth.getAccessToken`

Returns the access token from storage (if it exists).

#### `$auth.getIdToken`

Returns the ID token from storage (if it exists).

#### `$auth.getUser`

Returns the result of the OpenID Connect `/userinfo` endpoint if an access token exists.

#### `$auth.handleAuthentication`

Parses the tokens returned as hash fragments in the OAuth 2.0 Redirect URI.

## Development

1. Clone the repo:
- `git clone git@github.com:okta/okta-oidc-js.git`
2. Navigate into the `okta-vue` package:
Expand Down
2 changes: 1 addition & 1 deletion packages/okta-vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
],
"scripts": {
"prebuild": "npm run build:package-info",
"prestart": "npm run build",
"prestart": "npm run build && npm run build:harness",
"pretest": "npm run build && npm run build:harness",
"prepublish": "npm run build",
"jest": "jest src/",
Expand Down
13 changes: 9 additions & 4 deletions packages/okta-vue/src/Auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ function install (Vue, options) {
oktaAuth.userAgent = `${packageInfo.name}/${packageInfo.version} ${oktaAuth.userAgent}`

Vue.prototype.$auth = {
loginRedirect (additionalParams) {
loginRedirect (fromUri, additionalParams) {
if (fromUri) {
localStorage.setItem('referrerPath', fromUri)
}
return oktaAuth.token.getWithRedirect({
responseType: ['id_token', 'token'],
responseType: authConfig.response_type,
scopes: authConfig.scope.split(' '),
...additionalParams
})
Expand Down Expand Up @@ -63,8 +66,7 @@ function install (Vue, options) {
authRedirectGuard () {
return async (to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth) && !(await this.isAuthenticated())) {
localStorage.setItem('referrerPath', to.path || '/')
this.loginRedirect()
this.loginRedirect(to.path)
} else {
next()
}
Expand All @@ -82,6 +84,9 @@ const initConfig = auth => {
if (!auth.redirect_uri) missing.push('redirect_uri')
if (!auth.scope) auth.scope = 'openid'
if (missing.length) throw new Error(`${missing.join(', ')} must be defined`)

// Use space separated response_type or default value
auth.response_type = (auth.response_type || 'id_token token').split(' ')
return auth
}

Expand Down
38 changes: 35 additions & 3 deletions packages/okta-vue/src/Auth.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import AuthJS from '@okta/okta-auth-js'
import { createLocalVue } from '@vue/test-utils'
import { default as Auth } from './Auth'

const mockAuthJsInstance = {userAgent: 'foo'}
const pkg = require('../package.json')

jest.mock('@okta/okta-auth-js')

const mockAuthJsInstance = {
userAgent: 'foo',
token: {
getWithRedirect: jest.fn()
}
}

AuthJS.mockImplementation(() => {
return mockAuthJsInstance
})
Expand All @@ -17,11 +22,38 @@ describe('Auth', () => {
})
test('sets the right user agent on AuthJS', () => {
const expectedUserAgent = `${pkg.name}/${pkg.version} foo`
createLocalVue().use(Auth, {
const localVue = createLocalVue()
localVue.use(Auth, {
issuer: '1',
client_id: '2',
redirect_uri: '3'
})
expect(mockAuthJsInstance.userAgent).toMatch(expectedUserAgent)
})
test('sets the right scope and response_type when redirecting to Okta', () => {
const localVue = createLocalVue()
localVue.use(Auth, {
issuer: '1',
client_id: '2',
redirect_uri: '3'
})
localVue.prototype.$auth.loginRedirect()
const mockCallValues = mockAuthJsInstance.token.getWithRedirect.mock.calls[0][0]
expect(mockCallValues.responseType).toEqual(expect.arrayContaining(['id_token', 'token']))
expect(mockCallValues.scopes).toEqual(expect.arrayContaining(['openid']))
})
test('sets the right scope and response_type overrides when redirecting to Okta', () => {
const localVue = createLocalVue()
localVue.use(Auth, {
issuer: '1',
client_id: '2',
redirect_uri: '3',
scope: 'foo bar',
response_type: 'token'
})
localVue.prototype.$auth.loginRedirect()
const mockCallValues = mockAuthJsInstance.token.getWithRedirect.mock.calls[1][0]
expect(mockCallValues.responseType).toEqual(expect.arrayContaining(['token']))
expect(mockCallValues.scopes).toEqual(expect.arrayContaining(['foo', 'bar']))
})
})
2 changes: 1 addition & 1 deletion packages/okta-vue/src/components/ImplicitCallback.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export default {
name: 'ImplicitCallback',
async beforeMount () {
await this.$auth.handleAuthentication()
this.$router.push({
this.$router.replace({
path: this.$auth.getFromUri()
})
},
Expand Down
5 changes: 4 additions & 1 deletion packages/okta-vue/test/e2e/harness/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div id="app">
<router-link to="/" tag="button" id='home-button'> Home </router-link>
<button v-if='authenticated' v-on:click='logout' id='logout-button'> Logout </button>
<button v-else v-on:click='$auth.loginRedirect' id='login-button'> Login </button>
<button v-else v-on:click='login' id='login-button'> Login </button>
<router-link to="/protected" tag="button"> Protected </router-link>
<router-view/>
</div>
Expand All @@ -24,6 +24,9 @@ export default {
async isAuthenticated () {
this.authenticated = await this.$auth.isAuthenticated()
},
login () {
this.$auth.loginRedirect('/')
},
async logout () {
await this.$auth.logout()
await this.isAuthenticated()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default {
password: this.password
})
.then(res =>
this.$auth.loginRedirect({
this.$auth.loginRedirect('/protected', {
sessionToken: res.sessionToken
})
)
Expand Down
2 changes: 2 additions & 0 deletions packages/okta-vue/test/e2e/harness/test/e2e/specs/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
.setValue('#okta-signin-password', process.env.PASSWORD)
.click('#okta-signin-submit')
.waitForElementVisible('#app', 5000)
.assert.urlContains('/protected')
.assert.elementPresent('#logout-button')
.assert.containsText('.protected', 'Protected!')
.pause(2000) // Wait for async function to finish
Expand Down Expand Up @@ -52,6 +53,7 @@ module.exports = {
.setValue('#password', process.env.PASSWORD)
.click('#submit')
.waitForElementVisible('#logout-button', 5000)
.assert.urlContains('/protected')
.assert.elementPresent('#logout-button')
.click('#logout-button')
.end()
Expand Down