Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

splits TokenService into 2 services #1108

Closed
wants to merge 2 commits into from
Closed
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
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
30 changes: 22 additions & 8 deletions lib/OktaAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
ParseFromUrlInterface,
GetWithRedirectFunction,
RequestOptions,
isAuthenticatedOptions,
} from './types';
import {
transactionStatus,
Expand Down Expand Up @@ -102,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 @@ -154,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 @@ -350,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 @@ -567,33 +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();

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 (autoRenew) {
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 (autoRenew) {
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());
}
}
30 changes: 6 additions & 24 deletions lib/TokenManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,18 @@ import {
RefreshToken
} from './types';
import { REFRESH_TOKEN_STORAGE_KEY, TOKEN_STORAGE_NAME } from './constants';
import { TokenService } from './services/TokenService';


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 @@ -69,7 +71,6 @@ export class TokenManager implements TokenManagerInterface {
private storage: StorageProvider;
private state: TokenManagerState;
private options: TokenManagerOptions;
private service: TokenService | null;

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

options = Object.assign({}, DEFAULT_OPTIONS, removeNils(options));

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

start() {
if (this.service) {
this.stop();
}
if (this.options.clearPendingRemoveTokens) {
this.clearPendingRemoveTokens();
}
this.service = new TokenService(this, this.getOptions());
this.service.start();
}

stop() {
if (this.service) {
this.service.stop();
this.service = null;
}
}

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