Skip to content

Commit

Permalink
progress commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredperreault-okta committed Feb 22, 2022
1 parent bb4be5a commit 9e12e20
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 121 deletions.
90 changes: 68 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,21 @@ var config = {
}
```

#### `isAuthExpiredTokenBehavior`

When `oktaAuth.isAuthenticated` is called, a check is made to ensure tokens exist and are valid (not expired). With default configurations, if a token is expired an attempt is made to renew said token before the resulting `Promise` from `isAuthenticated` resolves. This configuration sets the behavior of `isAuthenticated`.

Possible Values: `'renew'`, `'remove'`, `'none'`

```javascript
// default config
var config = {
isAuthExpiredTokenBehavior: 'renew'
}
```

> This configuration can be overwritten on each `.isAuthenticated` call. See [isAuthenticated](#isauthenticatedoptions) for more info
#### `storageManager`

The `storageManager` provides access to client storage for specific purposes. `storageManager` configuration is divided into named sections. The default configuration is shown below:
Expand Down Expand Up @@ -697,18 +712,13 @@ var config = {
```

##### `autoRenew`
> :warning: Moved to [TokenService](#tokenservice). For backwards compatibility will set `services.tokenService.autoRenew`
> :gear: Requires a [running service](#running-as-a-service)
By default, the `tokenManager` will attempt to renew tokens before they expire. If you wish to manually control token renewal, set `autoRenew` to false to disable this feature. You can listen to [`expired`](#tokenmanageronevent-callback-context) events to know when the token has expired.
##### `expireEarlySeconds`

```javascript
tokenManager: {
autoRenew: false
}
```
> :warning: DEV ONLY
Renewing tokens slightly early helps ensure a stable user experience. By default, the `expired` event will fire 30 seconds before actual expiration time. If `autoRenew` is set to true, tokens will be renewed within 30 seconds of expiration. You can customize this value by setting the `expireEarlySeconds` option. The value should be large enough to account for network latency and clock drift between the client and Okta's servers.
To facilitate a more stable user experience, tokens are considered expired 30 seconds before actual expiration time. You can customize this value by setting the `expireEarlySeconds` option. The value should be large enough to account for network latency and clock drift between the client and Okta's servers.

**NOTE** `expireEarlySeconds` option is only allowed in the **DEV** environment (localhost). It will be reset to 30 seconds when running in environments other than **DEV**.

Expand All @@ -720,23 +730,17 @@ tokenManager: {
}
```

###### `autoRemove`

> :gear: Requires a [running service](#running-as-a-service)
By default, the library will attempt to remove expired tokens during initialization when `autoRenew` is off. If you wish to to disable auto removal of tokens, set autoRemove to false.
##### `autoRemove`
> :warning: Moved to [TokenService](#tokenservice). For backwards compatibility will set `services.tokenService.autoRenew`
##### `syncStorage`
> :warning: Moved to [SyncStorageService](#syncstorageservice). For backwards compatibility will set `services.syncStorageService.enable`
> :gear: Requires a [running service](#running-as-a-service)
Automatically syncs tokens across browser tabs when token storage is `localStorage`. To disable this behavior, set `syncStorage` to false.

###### `storageKey`
##### `storageKey`

By default all tokens will be stored under the key `okta-token-storage`. You may want to change this if you have multiple apps running on a single domain which share the same storage type. Giving each app a unique storage key will prevent them from reading or writing each other's token values.

###### `storage`
##### `storage`

Specify the [storage type](#storagetype) for tokens. This will override any value set for the `token` section in the [storageManager](#storagemanager) configuration. By default, [localStorage][] will be used. This will fall back to [sessionStorage][] or [cookie][] if the previous type is not available. You may pass an object or a string. If passing an object, it should meet the requirements of a [custom storage provider](#storage). Pass a string to specify one of the built-in storage types:

Expand Down Expand Up @@ -805,6 +809,42 @@ Defaults to `none` if the `secure` option is `true`, or `lax` if the `secure` op

Defaults to `true`, set this option to false if you want to opt-out of the default clearing pendingRemove tokens behaviour when `tokenManager.start()` is called.

## Services
> :gear: Requires a [running service](#running-as-a-service)
The following configurations require `OktaAuth` to be running as a service. See [running service](#running-as-a-service) for more info.

### TokenService

Default configuration:
```javascript
services: {
tokenService: {
autoRenew: true,
autoRemove: true
}
}
```

#### `autoRenew`
By default, the `TokenService` will attempt to renew tokens before they expire. If you wish to manually control token renewal, set `autoRenew` to `false` to disable this feature. You can listen to [`expired`](#tokenmanageronevent-callback-context) events to know when the token has expired.

> **NOTE** tokens are considered `expired` slightly before their actual expiration time. For more info, see [expireEarlySeconds](#expireearlyseconds).
#### `autoRemove`
By default, the library will attempt to remove expired tokens when `autoRenew` is `false`. If you wish to disable auto removal of tokens, set `autoRemove` to `false`.

### SyncStorageService
Automatically syncs tokens across browser tabs when token storage is `localStorage`. To disable this behavior, set `syncStorage.enable` to false.

Default configuration:
```javascript
services: {
syncStorageService: {
enable: true
}
}
```

## API Reference
<!-- no toc -->
* [start](#start)
Expand All @@ -821,7 +861,7 @@ Defaults to `true`, set this option to false if you want to opt-out of the defau
* [verifyRecoveryToken](#verifyrecoverytokenoptions)
* [webfinger](#webfingeroptions)
* [fingerprint](#fingerprintoptions)
* [isAuthenticated](#isauthenticatedtimeout)
* [isAuthenticated](#isauthenticatedoptions)
* [getUser](#getuser)
* [getIdToken](#getidtoken)
* [getAccessToken](#getaccesstoken)
Expand Down Expand Up @@ -1052,12 +1092,18 @@ authClient.fingerprint()
})
```

### `isAuthenticated(timeout?)`
### `isAuthenticated(options?)`

> :hourglass: async
Resolves with `authState.isAuthenticated` from non-pending [authState](#authstatemanager).

`options`
* `expiredTokenBehavior`: `'renew'` (default) | `'remove'` | `null`
* `'renew'` - attempt to renew token before `Promise` resolves
* `'remove'` - removes token
* `'none'` - neither renews or removes expired token

### `getUser()`

> :hourglass: async
Expand Down
32 changes: 22 additions & 10 deletions lib/OktaAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import {
ParseFromUrlInterface,
GetWithRedirectFunction,
RequestOptions,
AutoRenewServiceOptions,
isAuthenticatedOptions,
} from './types';
import {
transactionStatus,
Expand Down Expand Up @@ -103,6 +103,7 @@ import PromiseQueue from './PromiseQueue';
import fingerprint from './browser/fingerprint';
import { AuthStateManager } from './AuthStateManager';
import { StorageManager } from './StorageManager';
import { ServiceManager } from './ServiceManager';
import TransactionManager from './TransactionManager';
import { buildOptions } from './options';
import {
Expand Down Expand Up @@ -155,6 +156,7 @@ class OktaAuth implements OktaAuthInterface, SigninAPI, SignoutAPI {
emitter: typeof Emitter;
tokenManager: TokenManager;
authStateManager: AuthStateManager;
serviceManager: ServiceManager;
http: HttpAPI;
fingerprint: FingerprintAPI;
_oktaUserAgent: OktaUserAgent;
Expand Down Expand Up @@ -351,17 +353,20 @@ class OktaAuth implements OktaAuthInterface, SigninAPI, SignoutAPI {

// AuthStateManager
this.authStateManager = new AuthStateManager(this);

// ServiceManager
this.serviceManager = new ServiceManager(this);
}

start() {
this.tokenManager.start();
this.serviceManager.start();
if (!this.token.isLoginRedirect()) {
this.authStateManager.updateAuthState();
}
}

stop() {
this.tokenManager.stop();
this.serviceManager.stop();
}

setHeaders(headers) {
Expand Down Expand Up @@ -568,34 +573,41 @@ class OktaAuth implements OktaAuthInterface, SigninAPI, SignoutAPI {

// Returns true if both accessToken and idToken are not expired
// If `autoRenew` option is set, will attempt to renew expired tokens before returning.
async isAuthenticated(): Promise<boolean> {

async isAuthenticated(options: isAuthenticatedOptions = {}): Promise<boolean> {
let { accessToken, idToken } = this.tokenManager.getTokensSync();
// TODO: remove dependency on tokenManager config -
const { autoRenew, autoRemove } = this.tokenManager.getOptions();
const { enablePassiveRenew } = <AutoRenewServiceOptions>autoRenew;

let shouldRenewTokens = options.expiredTokenBehavior ? options.expiredTokenBehavior === 'renew' : autoRenew;
let shouldRemoveTokens = options.expiredTokenBehavior ? options.expiredTokenBehavior === 'remove' : autoRemove;

if (options?.expiredTokenBehavior === 'none') {
shouldRenewTokens = false;
shouldRemoveTokens = false;
}

if (accessToken && this.tokenManager.hasExpired(accessToken)) {
accessToken = undefined;
if (enablePassiveRenew) {
if (shouldRenewTokens) {
try {
accessToken = await this.tokenManager.renew('accessToken') as AccessToken;
} catch {
// Renew errors will emit an "error" event
}
} else if (autoRemove) {
} else if (shouldRemoveTokens) {
this.tokenManager.remove('accessToken');
}
}

if (idToken && this.tokenManager.hasExpired(idToken)) {
idToken = undefined;
if (enablePassiveRenew) {
if (shouldRenewTokens) {
try {
idToken = await this.tokenManager.renew('idToken') as IDToken;
} catch {
// Renew errors will emit an "error" event
}
} else if (autoRemove) {
} else if (shouldRemoveTokens) {
this.tokenManager.remove('idToken');
}
}
Expand Down
44 changes: 44 additions & 0 deletions lib/ServiceManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { OktaAuthOptions, ServiceInterface } from './types';
import { AutoRenewService, SyncStorageService } from './services';
import { OktaAuth } from '.';


export class ServiceManager {
private _sdk: OktaAuth;
private _options: OktaAuthOptions;
private _services: ServiceInterface[];

constructor (sdk: OktaAuth) {
this._sdk = sdk;
this._services = [];
this._options = { ...sdk.options, tokenManager: sdk.tokenManager.getOptions() };
}

get options() {
return {...this.options};
}

start() {
if (this._services.length > 0) {
this.stop();
}

const tokenManagerOptions = this._options.tokenManager;

if (tokenManagerOptions?.autoRenew) {
const autoRenewService = new AutoRenewService(this._sdk.tokenManager, {...tokenManagerOptions});
autoRenewService.start();
this._services.push(autoRenewService);
}

if (tokenManagerOptions?.syncStorage) {
const syncStorageService = new SyncStorageService(this._sdk.tokenManager, {...tokenManagerOptions});
syncStorageService.start();
this._services.push(syncStorageService);
}
}

stop() {
this._services.map(s => s.stop());
}
}
45 changes: 6 additions & 39 deletions lib/TokenManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,21 @@ import {
TokenManagerErrorEventHandler,
TokenManagerEventHandler,
TokenManagerInterface,
RefreshToken,
AutoRenewServiceOptions
RefreshToken
} from './types';
import { REFRESH_TOKEN_STORAGE_KEY, TOKEN_STORAGE_NAME } from './constants';
import { TokenService, AutoRenewService, SyncStorageService } from './services';


const DEFAULT_OPTIONS = {
autoRenew: true,
// TODO: remove these next major version
syncStorage: true,
autoRemove: true,
// --
enableActiveAutoRenew: true,
clearPendingRemoveTokens: true,
storage: undefined, // will use value from storageManager config
expireEarlySeconds: 30,
storageKey: TOKEN_STORAGE_NAME,
syncStorage: true,
_storageEventDelay: 0
};
export const EVENT_EXPIRED = 'expired';
Expand All @@ -70,7 +71,6 @@ export class TokenManager implements TokenManagerInterface {
private storage: StorageProvider;
private state: TokenManagerState;
private options: TokenManagerOptions;
private services: TokenService[];

on: (event: string, handler: TokenManagerErrorEventHandler | TokenManagerEventHandler, context?: object) => void;
off: (event: string, handler?: TokenManagerErrorEventHandler | TokenManagerEventHandler) => void;
Expand All @@ -81,15 +81,7 @@ export class TokenManager implements TokenManagerInterface {
if (!this.emitter) {
throw new AuthSdkError('Emitter should be initialized before TokenManager');
}
this.services = [];

options = Object.assign({}, DEFAULT_OPTIONS, removeNils(options));
if (!options.autoRenew) {
options.autoRenew = { enableActiveRenew: false, enablePassiveRenew: false };
}
else if (typeof options.autoRenew === 'boolean') {
options.autoRenew = { enableActiveRenew: true, enablePassiveRenew: true };
}

if (isIE11OrLess()) {
options._storageEventDelay = options._storageEventDelay || 1000;
Expand Down Expand Up @@ -118,31 +110,6 @@ export class TokenManager implements TokenManagerInterface {
this.off = this.emitter.off.bind(this.emitter);
}

start() {
if (this.services.length > 0) {
this.stop();
}
if (this.options.clearPendingRemoveTokens) {
this.clearPendingRemoveTokens();
}

if ((<AutoRenewServiceOptions>this.options.autoRenew!).enableActiveRenew) {
const autoRenewService = new AutoRenewService(this, this.getOptions());
autoRenewService.start();
this.services.push(autoRenewService);
}

if (this.options.syncStorage) {
const syncStorageService = new SyncStorageService(this, this.getOptions());
syncStorageService.start();
this.services.push(syncStorageService);
}
}

stop() {
this.services.map(s => s.stop());
}

getOptions(): TokenManagerOptions {
return clone(this.options);
}
Expand Down
Loading

0 comments on commit 9e12e20

Please sign in to comment.