Skip to content
Open
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
1 change: 1 addition & 0 deletions packages/@okta/vuepress-site/docs/guides/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ guides:
- keep-me-signed-in
- key-management
- manage-orgs-okta-aerial
- manage-user-creds
- mfa
- migrate-to-okta-prerequisites
- migrate-to-okta-bulk
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: Credential management using the Okta Client SDK
excerpt: Learn about how to securely manage user credentials using the Okta Client SDK.
layout: Guides
sections:
- main
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
---
title: Credential management using the Okta Client SDK
meta:
- name: description
content: Learn about how to securely manage user credentials using the Okta Client SDK.
---

Securely manage your tokens using the Okta JavaScript Client SDK, which provides a robust token management system designed to handle complex scenarios.

---

#### Learning outcomes

* Store credentials.
* Retrieve and work with credentials, including the default credential.
* Refresh user tokens.
* Remove credentials.

#### What you need

[The Okta JavaScript Client SDK configured for your app](https://github.com/okta/okta-client-javascript)

---

## Overview

The primary goal of using authentication APIs is to receive credentials that allow your users to interact with your app. If you don’t have some way to save and retrieve those credentials for later use, your users need to sign in to your app every time.

After the user (or other identity) is authenticated within an app, you must manage the token's lifecycle:

* Store it in a secure manner.
* Use it to perform requests on behalf of the user.
* Ensure that it's correctly refreshed as required.
* Remove it after reaching expiration or a direct deletion request.

The Okta JavaScript Client SDK provides a robust token management system designed to handle complex scenarios, such as multi-threaded access and data race conditions. This allows you to focus on your app's features rather than building a complex token management system from scratch.

### Okta Client SDK design principles

The Okta Client SDKs are designed as a modular library ecosystem to ensure architectural, naming, and feature parity across multiple languages, for example, Swift, Kotlin/Java, TypeScript/JavaScript, and .NET. It covers environments such as mobile, CLI, web, or backend.

* Abstraction: The SDK abstracts the complexities of OAuth 2.0 authentication flows, allowing you to focus on your UI rather than intricate business logic.
* Consistent interfaces: Authentication flows conform to common interfaces, for example, `start()` and `resume()` to simplify use, even for complex multi-step authentication.
* Token management system: The SDK provides a central token management system to simplify the secure storage, retrieval, validation, and lifecycle management of user credentials, handling multi-threaded access and biometrics requirements. This system aims to prevent issues like data race conditions during asynchronous operations.
* Extensibility: The SDK allows for customization of parameters, for example, `additionalParameters` as `[String: String]` dictionaries at various points in the authentication workflow, ensuring compatibility across different request body types.

## Core components of the token management system

The SDK's token management system is built on several key components that work together to provide a seamless and secure developer experience.

* `Credential`: This is the main convenience class for interacting with tokens. It acts as a runtime wrapper for a `Token` object, exposing simplified methods for the entire token lifecycle. Each `Credential` instance is uniquely tied to a corresponding `Token`.
* `Token`: An immutable data object representing the full set of OAuth 2.0 tokens (access token, refresh token, ID token) received from the authorization server. This object persists across app launches to keep your user signed in.
* `TokenStorage`: An interface responsible for the secure Create, Read, Update, and Delete (CRUD) operations of `Token` objects.
* `CredentialDataSource`: A factory that creates and caches `Credential` instances, ensuring that only one unique instance exists at runtime for any given `Token`.
* `CredentialCoordinator`: The central orchestrator that manages interactions between all of the above components to ensure data consistency.

## Store credentials

After a user successfully signs in, you receive a `Token` object. The first step is to securely store it:

* How it works: Use the static `Credential.store()` function. This saves the token using the configured TokenStorage mechanism and returns a `Credential` object for you to work with.
* Developer-assigned tags: You can add optional tags (metadata) during storage. These tags are useful for querying and managing multiple credentials later. For example, you could tag tokens based on user roles or the service they are for.

### JavaScript example: Store a token

```javascript
// Assume 'newToken' is a Token object received after a successful user sign in
try {
// Store the token with an optional tag
const credential = await Credential.store(newToken, ['service:purchase']);
// The 'credential' object now represents the user's stored session.
console.log('Credential stored successfully with ID:', credential.id);
} catch (error) {
// Handle storage error
console.error('Failed to store credential:', error);
}
```

## Retrieve and work with credentials

After storing the token, you need to retrieve credentials to maintain user sessions and authorize API requests. The SDK offers several convenient methods for this.

### Work with the default credential

For most single-user apps, you simplify the development process significantly by working with a "default" credential:

* The default can either be explicitly set or implicitly assigned when the first token is stored.
* Use the `Credential.getDefault()` static method to easily access the default credential, for example, to check if a user is already signed in when the app starts.
* The `Credential` class sends an event (for example, `default_changed`) whenever the default credential is set or removed. You can listen for this event to dynamically update your UI, such as transitioning from a sign-in page to a signed-in view.

#### JavaScript example: Check for a default user on app load

```javascript
// On app launch, check for a default credential
const credential = await Credential.getDefault();

if (credential) {
// User is signed in. Proceed to the main app view.
showUserProfile(credential);
} else {
// No default user. Show the sign-in page.
showLoginScreen();
}

// Listen for changes to the default credential
Credential.on('default_changed', async ({ id }) => {
if (id === null) {
console.log('User signed out, default credential removed.');
// Update UI to show signed-out state
} else {
const newCredential = await Credential.with(id);
console.log('Default credential has changed to:', newCredential?.id);
// Update UI with new user info
}
});
```

## Manage multiple credentials

The SDK is designed to support multi-user scenarios by allowing you to store and query multiple credentials simultaneously.

You can retrieve specific credentials using one of the following examples:

* Unique ID: Every stored token is assigned a locally generated unique ID. Use `Credential.with(id)` to fetch a specific credential. You can get a list of all stored IDs through the `Credential.allIDs()` function.
* Developer tags: Use `Credential.find({ tags: '...' })` to find credentials that match the tags that you assigned during token storage.
* ID token claims: You can also query credentials based on the claims within the ID token, such as the user's email (`sub`).

### JavaScript example: Find credentials

```javascript
// Find a credential using a tag
const purchaseCredentials = await Credential.find({ tags: 'service:purchase' });

if (purchaseCredentials.length > 0) {
const purchaseCredential = purchaseCredentials[0];
// Use the credential for a purchase API call
}

// Find all credentials for a specific user using an ID token claim
const userCredentials = await Credential.find(
metadata => metadata.claims?.sub === '[email protected]'
);
```

## Refresh user tokens

Access tokens are short-lived for security reasons. The SDK simplifies the process of using long-lived refresh tokens to get new access tokens without requiring the user to sign in again.

* `refresh()`: Immediately refreshes the token.
* `refreshIfNeeded()`: A smarter function that only refreshes the token if it has expired or is about to expire within a predefined grace interval. This is the recommended approach for proactively managing token expiration before making an API call.

### JavaScript example: Refresh a token before an API call

```javascript
async function makeAuthenticatedApiRequest(credential, url, data) {
try {
// Proactively refresh the token if it's close to expiring
await credential.refreshIfNeeded();

// Now, use the access token to authorize the request
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${credential.token.accessToken}`
},
body: JSON.stringify(data),
method: 'POST'
});
return response.json();

} catch (error) {
console.error('API request failed. User may need to re-authenticate.', error);
// If refresh fails (for example, refresh token is invalid), redirect the user to sign in
showLoginScreen();
}
}
```

## Remove credentials

When a user signs out or a session needs to be terminated, it's critical to properly remove the credentials from both the client and the server.

* `revoke()`: This function uses the Okta [revocation endpoint](https://developer.okta.com/docs/api/openapi/okta-oauth/oauth/tag/CustomAS/#tag/CustomAS/operation/revokeCustomAS) to invalidate the tokens on the authorization server. If successful, it automatically removes the credential from local storage. By default, this function revokes both the access and refresh tokens. This is the most secure way to sign a user out.
* `remove()`: This function only deletes the credential from the client-side `TokenStorage`. It doesn't invalidate the tokens on the server, and therefore poses a security risk if the tokens were compromised. Use this with caution.
* `SessionLogoutFlow()` or `signOut()`: For browser-based flows that create an Okta session cookie, you may need a specific sign-out function to clear the server-side session cookies in addition to revoking tokens.

### JavaScript example: Secure sign out

```javascript
async function handleSignOut(credential) {
try {
// Revoke both access and refresh tokens on the server
await credential.revoke();
console.log('User signed out and tokens revoked.');

// The credential is also automatically removed from local storage
// Now, redirect to the sign-in page
redirectTo('/signin');

} catch (error) {
console.error('Error during sign-out:', error);
// Even if server revocation fails, ensure that local state is cleared and redirect
await credential.remove(); // Failsafe local removal
redirectTo('/signin');
}
}
```

## Best practices

Use the following key recommendations for secure token management:

* Always use the <a href="https://developer.okta.com/docs/guides/implement-grant-type/authcodepkce/main/" target="_blank">Authorization Code Flow with PKCE</a>.
* Use <a href="https://developer.okta.com/docs/guides/oie-embedded-common-refresh-tokens/-/main/" target="_blank">short-lived access tokens</a>.
* Use and rotate <a href="https://developer.okta.com/docs/guides/refresh-tokens/main/#refresh-token-rotation/" target="_blank">refresh tokens</a>.
* Implement robust XSS and CSRF protections.
* Always <a href="https://developer.okta.com/docs/guides/revoke-tokens/main/" target="_blank">revoke tokens</a> when a user signs out.
* Register a <a href="https://developer.okta.com/docs/guides/custom-url-domain/main/" target="_blank">custom domain URL for your Okta org</a> to unlock branding capabilities and simplify session management.
* To mitigate risk and ensure proper access token use:
* Configure APIs with specific <a href="https://developer.okta.com/docs/guides/customize-authz-server/main/" target="_blank">authorization server</a> audiences, for example, `api.company.com/product1` instead of the base `api.company.com`.
* Use <a href="https://developer.okta.com/docs/guides/customize-authz-server/main/#create-scopes" target="_blank">granular scopes</a>, for example, `com.okta.product1.admin` instead of a generic administrator scope.
4 changes: 4 additions & 0 deletions packages/@okta/vuepress-theme-prose/const/navbar.const.js
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,10 @@ export const guides = [
title: "Configure SSO for native apps",
guideName: "configure-native-sso",
},
{
title: "Credential management using the Okta Client SDK",
guideName: "manage-user-creds",
},
{
title: "Request user consent",
guideName: "request-user-consent"
Expand Down