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

📦 Prepping 1.0.0 angular #157

Merged
merged 9 commits into from
Mar 27, 2018
107 changes: 77 additions & 30 deletions packages/okta-angular/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
# Okta Angular SDK

An Angular (4+) wrapper around [Okta Auth JS](https://github.com/okta/okta-auth-js), that 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/).
* An Okta Application, configured for Singe-Page App (SPA) mode. This is done from the Okta Developer Console and you can find instructions [here](https://developer.okta.com/authentication-guide/implementing-authentication/implicit#1-setting-up-your-application). When following the wizard, use the default properties. They are are designed to work with our sample applications.

- 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/).
- An Okta Application, configured for Singe-Page App (SPA) mode. This is done from the Okta Developer Console and you can find instructions [here](https://developer.okta.com/authentication-guide/implementing-authentication/implicit#1-setting-up-your-application). When following the wizard, use the default properties. They are are designed to work with our sample applications.

### Helpful Links

- [Angular Quickstart](https://angular.io/guide/quickstart)
- If you don't have an Angular app, or are new to Angular, please start with this guide. It will walk you through the creation of an Angular app, creating routes, and other application development essentials.
- [Okta Sample Application](https://github.com/okta/samples-js-angular)
Expand All @@ -17,27 +21,33 @@ This library currently supports:
- Helpful resource for integrating an existing Angular application into Okta.

## Installation

This library is available through [npm](https://www.npmjs.com/package/@okta/okta-angular). To install it, simply add it to your project:

```bash
npm install --save @okta/okta-angular
```

## Usage

`okta-angular` works directly with [`@angular/router`](https://angular.io/guide/router) and provides the additional components and services:

- [`OktaAuthModule`](#oktaauthmodule) - Allows you to supply your OpenID Connect client configuration.
- [`OktaAuthGuard`](#oktaauthguard) - A navigation guard using [CanActivate](https://angular.io/api/router/CanActivate) to grant access to a page only after successful authentication.
- [`OktaCallbackComponent`](#oktacallbackcomponent) - Handles the implicit flow callback by parsing tokens from the URL and storing them automatically.
- [`OktaLoginRedirectComponent`](#oktaloginredirectcomponent) - Redirects users to the Okta Hosted Login Page for authentication.
- [`OktaAuthService`](#oktaauthservice) - Highest-level service containing the `okta-angular` public methods.

### `OktaAuthModule`

The `OktaAuthModule` is the initializer for your OpenID Connect client configuration. It accepts the following properties:
- `issuer` **(required)**: The OpenID Connect `issuer`
- `clientId` **(required)**: The OpenID Connect `client_id`
- `redirectUri` **(required)**: Where the callback is hosted
- `scope` *(optional)*: Reserved for custom claims to be returned in the tokens
- `onAuthRequired` *(optional)*: Accepts a callback to make a decision when authentication is required. If not supplied, `okta-angular` will redirect directly to Okta for authentication.

- `issuer` **(required)**: The OpenID Connect `issuer`
- `clientId` **(required)**: The OpenID Connect `client_id`
- `redirectUri` **(required)**: Where the callback is hosted
- `scope` *(optional)*: Reserved for custom claims to be returned in the tokens
- `responseType` *(optional)*: Desired token grant types
- `onAuthRequired` *(optional)*: Accepts a callback to make a decision when authentication is required. If not supplied, `okta-angular` will redirect directly to Okta for authentication.

```typescript
// myApp.module.ts
Expand All @@ -48,8 +58,8 @@ import {

const oktaConfig = {
issuer: 'https://{yourOktaDomain}.com/oauth2/default',
redirectUri: 'http://localhost:{port}/implicit/callback',
clientId: '{clientId}'
clientId: '{clientId}',
redirectUri: 'http://localhost:{port}/implicit/callback'
}

const appRoutes: Routes = [
Expand All @@ -66,6 +76,7 @@ export class MyAppModule { }
```

### `OktaAuthGuard`

Routes are protected by the `OktaAuthGuard`, which verifies there is a valid `accessToken` stored. To ensure the user has been authenticated before accessing your route, add the `canActivate` guard to one of your routes:

```typescript
Expand All @@ -89,6 +100,7 @@ const appRoutes: Routes = [
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.

### `OktaCallbackComponent`

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 specify the default `OktaCallbackComponent` and declare it in your `NgModule`.

```typescript
Expand Down Expand Up @@ -116,6 +128,7 @@ const appRoutes: Routes = [
```

### `OktaLoginRedirectComponent`

By default, the `OktaLoginRedirect` component redirects users to your Okta organization for login. Simply import and add it to your `appRoutes` to offset authentication to Okta entirely:

```typescript
Expand All @@ -135,6 +148,7 @@ const appRoutes: Routes = [
```

#### Using a custom login-page

The `okta-angular` 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 modify the unauthentication callback functionality by adding a `data` attribute directly to your `Route`:
Expand All @@ -160,7 +174,6 @@ const appRoutes: Routes = [
canActivate: [ OktaAuthGuard ],
data: {
onAuthRequired: onAuthRequired
}
}
}
]
Expand All @@ -177,6 +190,7 @@ const oktaConfig = {
```

### `OktaAuthService`

In your components, your can take advantage of all of `okta-angular`'s features by importing the `OktaAuthService`. The example below shows connecting two buttons to handle **login** and **logout**:

```typescript
Expand All @@ -187,55 +201,84 @@ import { OktaAuthService } from '@okta/okta-angular';
@Component({
selector: 'app-component',
template: `
<button *ngIf="!oktaAuth.isAuthenticated()" (click)="oktaAuth.loginRedirect('/profile')> Login </button>
<button *ngIf="oktaAuth.isAuthenticated()" (click)="oktaAuth.logout('/')"> Logout </button>
<button *ngIf="!isAuthenticated" (click)="login()">Login</button>
<button *ngIf="isAuthenticated" (click)="logout()">Logout</button>

<router-outlet></router-outlet>
<router-outlet></router-outlet>
`,
})
export class MyComponent {

isAuthenticated: boolean;
constructor(public oktaAuth: OktaAuthService) {
// ...
// get authentication state for immediate use
await this.isAuthenticated = this.oktaAuth.isAuthenticated();

// subscribe to authentication state changes
this.oktaAuth.$authenticatedState.subscribe(
(isAuthenticated: boolean) => this.isAuthenticated = isAuthenticated
);
}
login() {
this.oktaAuth.loginRedirect('/profile');
}
logout() {
this.oktaAuth.logout('/');
}
}
```

#### `oktaAuth.loginRedirect(additionalParams?)`
Performs a full page redirect to Okta based on the initial configuration.
#### `oktaAuth.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 optional 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.oktaAuth.loginRedirect({
this.oktaAuth.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).

#### `oktaAuth.isAuthenticated()`
Returns `true` if there is a valid access token or ID token.

#### `oktaAuth.getUser()`
Returns the result of the OpenID Connect `/userinfo` endpoint if an access token is provided or parses the available idToken.
Returns a promise that resolves `true` if there is a valid access token or ID token.

#### `oktaAuth.getAccessToken()`
Returns the access token from storage (if it exists).
#### `oktaAuth.$authenticationState`

#### `oktaAuth.getIdToken()`
Returns the ID token from storage (if it exists).
An observable that returns true/false when the authenticate state changes. This will happen after a successful login via `oktaAuth.handleAuthentication()` or logout via `oktaAuth.logout()`.

#### `oktaAuth.getUser()`
Returns the result of the OpenID Connect `/userinfo` endpoint if an access token exists.

Returns a promise that will resolve with the result of the OpenID Connect `/userinfo` endpoint if an access token is provided, or returns the claims of the ID token if no access token is available. The returned claims depend on the requested response type, requested scope, and authorization server policies. For more information see documentation for the [UserInfo endpoint][], [ID Token Claims][], and [Customizing Your Authorization Server][].

#### `oktaAuth.getAccessToken() Promise<string>`

Returns a promise that returns the access token string from storage (if it exists).

#### `oktaAuth.getIdToken() Promise<string>`

Returns a promise that returns the ID token string from storage (if it exists).

#### `oktaAuth.handleAuthentication()`
Parses the tokens returned as hash fragments in the OAuth 2.0 Redirect URI, then redirects to the URL specified when calling `loginRedirect`.

#### `oktaAuth.logout()`
Terminates the user's session in Okta and clears all stored tokens.
Parses the tokens returned as hash fragments in the OAuth 2.0 Redirect URI, then redirects to the URL specified when calling `loginRedirect`. Returns a promise that will be resolved when complete.

#### `oktaAuth.logout(uri?)`

Terminates the user's session in Okta and clears all stored tokens. Accepts an optional `uri` parameter to push the user to after logout.

#### `oktaAuth.setFromUri(uri, queryParams)`

Used to capture the current URL state before a redirect occurs. Used primarily for custom [`canActivate`](https://angular.io/api/router/CanActivate) navigation guards.

#### `oktaAuth.getFromUri()`

Returns the stored URI and query parameters stored when the `OktaAuthGuard` and/or `setFromUri` was used.

## Development

1. Clone the repo:
- `git clone git@github.com:okta/okta-oidc-js.git`
2. Navigate into the `okta-angular` package:
Expand All @@ -253,3 +296,7 @@ Terminates the user's session in Okta and clears all stored tokens.
| `npm test` | Run integration tests |
| `npm run lint` | Run eslint linting tests |
| `npm run docs` | Generate typedocs |

[ID Token Claims]: https://developer.okta.com/docs/api/resources/oidc#id-token-claims
[UserInfo endpoint]: https://developer.okta.com/docs/api/resources/oidc#userinfo
[Customizing Your Authorization Server]: https://developer.okta.com/authentication-guide/implementing-authentication/set-up-authz-server
4 changes: 3 additions & 1 deletion packages/okta-angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
"prepublish": "npm run ngc",
"prengc": "npm run build:package-info",
"ngc": "./node_modules/.bin/ngc -p tsconfig.json",
"test": "npm run --prefix test/e2e/harness/ e2e",
"test": "npm run lint && npm run test:e2e && npm run test:unit",
"test:unit": "npm run --prefix test/e2e/harness/ test",
"test:e2e": "npm run --prefix test/e2e/harness/ e2e",
"start": "npm run --prefix test/e2e/harness/ start",
"docs": "typedoc --options typedoc.json --exclude '{**/*.spec.ts,**/test/**}' ./src/",
"lint": "npm run --prefix test/e2e/harness/ lint",
Expand Down
5 changes: 3 additions & 2 deletions packages/okta-angular/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@

export { OktaAuthModule } from './okta/okta.module';
export { OktaAuthGuard } from './okta/okta.guard';
export { OktaAuthService } from './okta/okta.service';
export { OKTA_CONFIG } from './okta/okta.config';
export { OktaAuthService } from './okta/services/okta.service';
export { OKTA_CONFIG } from './okta/models/okta.config';
export { UserClaims } from './okta/models/user-claims';

// Okta View Components
export { OktaCallbackComponent, OktaLoginRedirectComponent } from './okta/components';
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import { Component } from '@angular/core';

import { OktaAuthService } from '../okta.service';
import { OktaAuthService } from '../services/okta.service';

@Component({template: `` })
export class OktaCallbackComponent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import { Component } from '@angular/core';

import { OktaAuthService } from '../okta.service';
import { OktaAuthService } from '../services/okta.service';

@Component({ template: `` })
export class OktaLoginRedirectComponent {
Expand Down
17 changes: 17 additions & 0 deletions packages/okta-angular/src/okta/models/auth-required-function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved.
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
*
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and limitations under the License.
*/

import { Router } from '@angular/router';

import { OktaAuthService } from '../services/okta.service';

export type AuthRequiredFunction = (oktaAuth: OktaAuthService, router: Router) => void;
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@

import { InjectionToken } from '@angular/core';

import { AuthRequiredFunction } from './auth-required-function';

export interface OktaConfig {
issuer?: string;
redirectUri?: string;
clientId?: string;
scope?: string;
onAuthRequired?: Function;
responseType?: string;
onAuthRequired?: AuthRequiredFunction;
}

export const OKTA_CONFIG = new InjectionToken<OktaConfig>('okta.config.angular');
29 changes: 29 additions & 0 deletions packages/okta-angular/src/okta/models/user-claims.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* @export
* @interface UserClaims
*
* This interface represents the union of possible known claims that are in an
* ID Token or returned from the /userinfo response and depend on the
* response_type and scope parameters in the authorize request
*/
export interface UserClaims {
auth_time?: Number;
aud?: string;
email?: string;
email_verified?: Boolean;
exp?: Number;
family_name?: string;
given_name?: string;
iat?: Number;
iss?: string;
jti?: string;
locale?: string;
name?: string;
nonce?: string;
preferred_username?: string;
sub: string;
updated_at?: Number;
ver?: Number;
zoneinfo?: string;
[propName: string]: any; // For custom claims that may be configured by the org admin
}
34 changes: 13 additions & 21 deletions packages/okta-angular/src/okta/okta.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,47 +18,39 @@ import {
Router
} from '@angular/router';

import { OktaAuthService } from './okta.service';
import { OktaAuthService } from './services/okta.service';
import { AuthRequiredFunction } from './models/auth-required-function';

@Injectable()
export class OktaAuthGuard implements CanActivate {
private oktaAuth: OktaAuthService;

constructor(private okta: OktaAuthService, private router: Router) {
this.oktaAuth = okta;
}
constructor(private oktaAuth: OktaAuthService, private router: Router) { }

/**
* Gateway for protected route. Returns true if there is a valid accessToken,
* otherwise it will cache the route and start the login flow.
* @param route
* @param state
* @param route
* @param state
*/
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (this.oktaAuth.isAuthenticated()) {
if (await this.oktaAuth.isAuthenticated()) {
return true;
}

/**
* Get the operation to perform on failed authentication from
* either the global config or route data injection.
*/
const onAuthRequired = route.data['onAuthRequired'] || this.oktaAuth.getOktaConfig().onAuthRequired;

if (onAuthRequired){
onAuthRequired(this.oktaAuth, this.router);
}
const onAuthRequired: AuthRequiredFunction = route.data['onAuthRequired'] || this.oktaAuth.getOktaConfig().onAuthRequired;

/**
/**
* Store the current path
*/
this.oktaAuth.setFromUri(state.url);
const path = state.url.split(/[?#]/)[0];
this.oktaAuth.setFromUri(path, route.queryParams);

/**
* Redirect to the given path or
* perform the default Okta full-page redirect.
*/
if (!onAuthRequired) {
if (onAuthRequired) {
onAuthRequired(this.oktaAuth, this.router);
} else {
this.oktaAuth.loginRedirect();
}

Expand Down
Loading