@axa-fr/oidc-client
v7.23.1
Published
OpenID Connect & OAuth authentication using native javascript only, compatible with angular, react, vue, svelte, next, etc.
Downloads
104,191
Readme
@axa-fr/oidc-client
@axa-fr/oidc-client the lightest and securest library to manage authentication with OpenID Connect (OIDC) and OAuth2 protocol. It is compatible with all OIDC providers. @axa-fr/oidc-client is a pure javascript library. It works with any JavaScript framework or library.
We provide a wrapper @axa-fr/react-oidc for React (compatible next.js) and we expect soon to provide one for Vue, Angular and Svelte.
- Try the react demo at https://black-rock-0dc6b0d03.1.azurestaticapps.net/ (most advanced)
- Try the pure javascript demo at https://icy-glacier-004ab4303.2.azurestaticapps.net/
About
@axa-fr/oidc-client is:
- Secure :
- With Demonstrating Proof of Possession (DPoP), your access_token and refresh_token are not usable outside your browser context (big protection)
- With the use of Service Worker, your tokens (refresh_token and/or access_token) are not accessible to the JavaScript client code (if you follow good practices from
FAQ
section) - OIDC using client side Code Credential Grant with pkce only
- Lightweight : Unpacked Size on npm is 274 kB
- Simple
- refresh_token and access_token are auto refreshed in background
- with the use of the Service Worker, you do not need to inject the access_token in every fetch, you have only to configure OidcTrustedDomains.js file
- Multiple Authentication :
- You can authenticate many times to the same provider with different scope (for example you can acquire a new 'payment' scope for a payment)
- You can authenticate to multiple different providers inside the same SPA (single page application) website
- Flexible :
- Work with Service Worker (more secure) and without for older browser (less secure).
- You can disable Service Worker if you want (but less secure) and just use SessionStorage or LocalStorage mode.
The service worker catch access_token and refresh_token that will never be accessible to the client.
Getting Started
npm install @axa-fr/oidc-client --save
# To install or update OidcServiceWorker.js file, you can run
node ./node_modules/@axa-fr/oidc-client/bin/copy-service-worker-files.mjs public
# If you have a "public" folder, the 2 files will be created :
# ./public/OidcServiceWorker.js <-- will be updated at each "npm install"
# ./public/OidcTrustedDomains.js <-- won't be updated if already exist
WARNING : If you use Service Worker mode, the OidcServiceWorker.js file should always be up to date with the version of the library. You may setup a postinstall script in your package.json file to update it at each npm install. For example :
"scripts": {
...
"postinstall": "node ./node_modules/@axa-fr/oidc-client/bin/copy-service-worker-files.mjs public"
},
If you need a very secure mode where refresh_token and access_token will be hide behind a service worker that will proxify requests. The only file you should edit is "OidcTrustedDomains.js".
// OidcTrustedDomains.js
// Add bellow trusted domains, access tokens will automatically injected to be send to
// trusted domain can also be a path like https://www.myapi.com/users,
// then all subroute like https://www.myapi.com/useers/1 will be authorized to send access_token to.
// Domains used by OIDC server must be also declared here
const trustedDomains = {
default: {
oidcDomains: ['https://demo.duendesoftware.com'],
accessTokenDomains: ['https://www.myapi.com/users'],
},
};
// Service worker will continue to give access token to the JavaScript client
// Ideal to hide refresh token from client JavaScript, but to retrieve access_token for some
// scenarios which require it. For example, to send it via websocket connection.
trustedDomains.config_show_access_token = {
oidcDomains: ['https://demo.duendesoftware.com'],
accessTokenDomains: ['https://www.myapi.com/users'],
showAccessToken: false,
// convertAllRequestsToCorsExceptNavigate: false, // default value is false
// setAccessTokenToNavigateRequests: true, // default value is true
};
// DPoP (Demonstrating Proof of Possession) will be activated for the following domains
trustedDomains.config_with_dpop = {
domains: ['https://demo.duendesoftware.com'],
demonstratingProofOfPossession: true,
demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent: true, // default value is false, inject DPOP token only when DPOP header is present
// Optional, more details bellow
/*demonstratingProofOfPossessionConfiguration: {
importKeyAlgorithm: {
name: 'ECDSA',
namedCurve: 'P-256',
hash: {name: 'ES256'}
},
signAlgorithm: {name: 'ECDSA', hash: {name: 'SHA-256'}},
generateKeyAlgorithm: {
name: 'ECDSA',
namedCurve: 'P-256'
},
digestAlgorithm: { name: 'SHA-256' },
jwtHeaderAlgorithm : 'ES256'
}*/
};
// Setting allowMultiTabLogin to true will enable storing login-specific parameters (state, nonce, code verifier)
// separately for each tab. This will prevent errors when logins are initiated from multiple tabs.
trustedDomains.config_multi_tab_login = {
domains: ['https://demo.duendesoftware.com'],
allowMultiTabLogin: true,
};
The code of the demo :
import { OidcClient } from '@axa-fr/oidc-client';
export const configuration = {
client_id: 'interactive.public.short',
redirect_uri: window.location.origin + '/#/authentication/callback',
silent_redirect_uri: window.location.origin + '/#/authentication/silent-callback',
scope: 'openid profile email api offline_access',
authority: 'https://demo.duendesoftware.com',
service_worker_relative_url: '/OidcServiceWorker.js', // just comment that line to disable service worker mode
service_worker_only: false,
demonstrating_proof_of_possession: false,
};
const href = window.location.href;
const oidcClient = OidcClient.getOrCreate()(configuration);
// Use the fetch bellow to inject access_token and DPOP tokens automatically
const oidcFetch = oidcClient.fetchWithTokens(fetch);
// You can inject you own fetch (default Fetch Interface) function and location object (respecting IOidcLocation interface)
// import {OidcLocation} from '@axa-fr/oidc-client'
// const oidcClient = OidcClient.getOrCreate(() => fetch, new OidcLocation())(configuration);
console.log(href);
oidcClient.tryKeepExistingSessionAsync().then(() => {
if (href.includes(configuration.redirect_uri)) {
oidcClient.loginCallbackAsync().then(() => {
window.location.href = '/';
});
document.body.innerHTML = `<div>
<h1>@axa-fr/oidc-client demo</h1>
<h2>Loading</h2>
</div>`;
return;
}
let tokens = oidcClient.tokens;
if (tokens) {
// @ts-ignore
window.logout = () => oidcClient.logoutAsync();
document.body.innerHTML = `<div>
<h1>@axa-fr/oidc-client demo</h1>
<button onclick="window.logout()">Logout</button>
<h2>Authenticated</h2>
<pre>${JSON.stringify(tokens, null, '\t')}</pre>
</div>`;
} else {
// @ts-ignore
window.login = () => oidcClient.loginAsync('/');
document.body.innerHTML = `<div>
<h1>@axa-fr/oidc-client demo</h1>
<button onclick="window.login()">Login</button>
</div>`;
}
});
Configuration
const configuration = {
client_id: String.isRequired, // oidc client id
redirect_uri: String.isRequired, // oidc redirect url
silent_redirect_uri: String, // Optional activate silent-signin that use cookies between OIDC server and client javascript to restore sessions
silent_login_uri: String, // Optional, route that triggers the signin
silent_login_timeout: Number, // Optional, default is 12000 milliseconds
scope: String.isRequired, // oidc scope (you need to set "offline_access")
authority: String.isRequired,
storage: Storage, // Default sessionStorage, you can set localStorage, but it is less secure against XSS attacks
authority_configuration: {
// Optional for providers that do not implement OIDC server auto-discovery via a .wellknown URL
authorization_endpoint: String,
token_endpoint: String,
userinfo_endpoint: String,
end_session_endpoint: String,
revocation_endpoint: String,
check_session_iframe: String,
issuer: String,
},
refresh_time_before_tokens_expiration_in_second: Number, // default is 120 seconds
service_worker_relative_url: String,
service_worker_keep_alive_path: String, // default is "/"
service_worker_only: Boolean, // default false, if true, the user will not be able to login if the service worker is not available on its browser
service_worker_activate: () => boolean, // you can take the control of the service worker default activation which use user agent string, if return false, the service worker mode will not be used
service_worker_update_require_callback: (registration:any, stopKeepAlive:Function) => Promise<void>, // callback called when service worker need to be updated, you can take the control of the update process
service_worker_register: (url: string) => Promise<ServiceWorkerRegistration>, // Optional, you can take the control of the service worker registration
extras: StringMap | undefined, // ex: {'prompt': 'consent', 'access_type': 'offline'} list of key/value that is sent to the OIDC server (more info: https://github.com/openid/AppAuth-JS)
token_request_extras: StringMap | undefined, // ex: {'prompt': 'consent', 'access_type': 'offline'} list of key/value that is sent to the OIDC server during token request (more info: https://github.com/openid/AppAuth-JS)
authority_time_cache_wellknowurl_in_second: 60 * 60, // Time to cache in seconds of the openid well-known URL, default is 1 hour
authority_timeout_wellknowurl_in_millisecond: 10000, // Timeout in milliseconds of the openid well-known URL, default is 10 seconds, then an error is thrown
monitor_session: Boolean, // Add OpenID monitor session, default is false (more information https://openid.net/specs/openid-connect-session-1_0.html), if you need to set it to true consider https://infi.nl/nieuws/spa-necromancy/
token_renew_mode: String, // Optional, update tokens based on the selected token(s) lifetime: "access_token_or_id_token_invalid" (default), "access_token_invalid", "id_token_invalid"
token_automatic_renew_mode: TokenAutomaticRenewMode.AutomaticOnlyWhenFetchExecuted, // Optional, default is TokenAutomaticRenewMode.AutomaticBeforeTokensExpiration
// TokenAutomaticRenewMode.AutomaticBeforeTokensExpiration: renew tokens automatically before they expire
// TokenAutomaticRenewMode.AutomaticOnlyWhenFetchExecuted: renew tokens automatically only when fetch is executed
// It requires you to use fetch given by oidcClient.fetchWithTokens(fetch) or to use oidcClient.getValidTokenAsync()
logout_tokens_to_invalidate: Array<string>, // Optional tokens to invalidate during logout, default: ['access_token', 'refresh_token']
location: ILOidcLocation, // Optional, default is window.location, you can inject your own location object respecting the ILOidcLocation interface
demonstrating_proof_of_possession: Boolean, // Optional, default is false, if true, the the Demonstrating Proof of Possession will be activated //https://www.rfc-editor.org/rfc/rfc9449.html#name-protected-resource-access
demonstrating_proof_of_possession_configuration: DemonstratingProofOfPossessionConfiguration // Optional, more details bellow
};
interface DemonstratingProofOfPossessionConfiguration {
generateKeyAlgorithm: RsaHashedKeyGenParams | EcKeyGenParams,
digestAlgorithm: AlgorithmIdentifier,
importKeyAlgorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm,
signAlgorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams,
jwtHeaderAlgorithm: string
};
// default value of demonstrating_proof_of_possession_configuration
const defaultDemonstratingProofOfPossessionConfiguration: DemonstratingProofOfPossessionConfiguration ={
importKeyAlgorithm: {
name: 'ECDSA',
namedCurve: 'P-256',
hash: {name: 'ES256'}
},
signAlgorithm: {name: 'ECDSA', hash: {name: 'SHA-256'}},
generateKeyAlgorithm: {
name: 'ECDSA',
namedCurve: 'P-256'
},
digestAlgorithm: { name: 'SHA-256' },
jwtHeaderAlgorithm : 'ES256'
};
API
/**
* OidcClient is a class that acts as a wrapper around the `Oidc` object. It provides methods to handle event subscriptions, logins, logouts, token renewals, user information, etc.
*/
export class OidcClient {
/**
* Creates an instance of OidcClient using a provided `Oidc` object.
* @param oidc The instance of the underlying Oidc object to use.
*/
constructor(oidc: Oidc);
/**
* Subscribes a function to events emitted by the underlying Oidc object.
* @param func The function to be called when an event is emitted.
* @returns A string that identifies the subscription and can be used to unsubscribe later.
*/
subscribeEvents(func: EventSubscriber): string;
/**
* Removes a subscription to a specified event.
* @param id The identifier of the subscription to remove, obtained during the initial subscription.
*/
removeEventSubscription(id: string): void;
/**
* Publishes an event with the specified name and associated data.
* @param eventName The name of the event to publish.
* @param data The data associated with the event.
*/
publishEvent(eventName: string, data: any): void;
/**
* Creates a new instance of OidcClient using a fetch retrieval function `getFetch`, with a given OIDC configuration and an optional name.
* @param getFetch The function to retrieve the `Fetch` object.
* @param configuration The OIDC configuration to use for creating the OidcClient instance.
* @param name The optional name for the created OidcClient instance.
* @returns A new instance of OidcClient with the specified configuration.
*/
static getOrCreate(getFetch: () => Fetch)(configuration: OidcConfiguration, name?: string): OidcClient;
/**
* Retrieves an existing OidcClient instance with the specified name, or creates a new instance if it does not exist.
* @param name The name of the OidcClient instance to retrieve.
* @returns The existing OidcClient instance or a new instance with the specified name.
*/
static get(name?: string): OidcClient;
/**
* The names of the events supported by the Oidc class.
*/
static eventNames: Oidc.eventNames;
/**
* Attempts to keep the existing user session by calling the function of the underlying Oidc object.
* @returns A promise resolved with `true` if the user session was kept, otherwise `false`.
*/
tryKeepExistingSessionAsync(): Promise<boolean>;
/**
* Starts the OIDC login process with specified options.
* @param callbackPath The callback path for authentication.
* @param extras Additional parameters to send to the OIDC server during the login request.
* @param isSilentSignin Indicates if the login is silent.
* @param scope The OIDC scope for the login request.
* @param silentLoginOnly Indicates if only silent login is allowed.
* @returns A promise resolved with the login information, or rejected with an error.
*/
loginAsync(callbackPath?: string, extras?: StringMap, isSilentSignin?: boolean, scope?: string, silentLoginOnly?: boolean): Promise<unknown>;
/**
* Starts the OIDC logout process with specified options.
* @param callbackPathOrUrl The callback path or URL to use after logout.
* @param extras Additional parameters to send to the OIDC server during the logout request.
* {"no_reload:oidc":"true"} to avoid the page reload after logout.
* you can add extras like {"client_secret:revoke_refresh_token":"secret"} to revoke the refresh token with extra client secret. Any key ending with ":revoke_refresh_token" will be used to revoke the refresh token.
* you can add extras like {"client_secret:revoke_access_token":"secret"} to revoke the access token with extra client secret. Any key ending with ":revoke_access_token" will be used to revoke the access token.
* @returns A promise resolved when the logout is completed.
*/
logoutAsync(callbackPathOrUrl?: string | null | undefined, extras?: StringMap): Promise<void>;
/**
* Performs the silent login process and retrieves user information.
* @returns A promise resolved when the silent login process is completed.
*/
silentLoginCallbackAsync(): Promise<void>;
/**
* Renews the user's OIDC tokens.
* @param extras Additional parameters to send to the OIDC server during the token renewal request.
* @returns A promise resolved when the token renewal is completed.
*/
renewTokensAsync(extras?: StringMap): Promise<void>;
/**
* Performs the callback process after a successful login and automatically renews tokens.
* @returns A promise resolved with the callback information, or rejected with an error.
*/
loginCallbackAsync(): Promise<LoginCallback>;
/**
* Retrieves the current OIDC tokens for the user.
*/
get tokens(): Tokens;
/**
* Retrieves the current OIDC configuration used by the OidcClient instance.
*/
get configuration(): OidcConfiguration;
/**
* Retrieves the valid OIDC token for the user.
* @param waitMs The maximum wait time in milliseconds to obtain a valid token.
* @param numberWait The number of attempts to obtain a valid token.
* @returns A promise resolved with the valid token, or rejected with an error.
*/
async getValidTokenAsync(waitMs = 200, numberWait = 50): Promise<ValidToken>;
/**
* Retrieves a new fetch function that inject bearer tokens (also DPOP tokens).
* @param fetch The current fetch function to use
* @param demonstrating_proof_of_possession Indicates whether the demonstration of proof of possession should be used.
* @returns Fetch A new fectch function that inject bearer tokens (also DPOP tokens).
*/
fetchWithTokens(fetch: Fetch, demonstrating_proof_of_possession=false): Fetch;
/**
* Retrieves OIDC user information.
* @param noCache Indicates whether user information should be retrieved bypassing the cache.
* @param demonstrating_proof_of_possession Indicates whether the demonstration of proof of possession should be used.
* @returns A promise resolved with the user information, or rejected with an error.
*/
async userInfoAsync<T extends OidcUserInfo = OidcUserInfo>(noCache = false, demonstrating_proof_of_possession=false): Promise<T>;
/**
* Generate Demonstration of proof of possession.
* @param accessToken The access token to use.
* @param url The url to use.
* @param method The method to use.
* @param extras Additional parameters to send to the OIDC server during the demonstration of proof of possession request.
* @returns A promise resolved with the proof of possession.
*/
async generateDemonstrationOfProofOfPossessionAsync(accessToken:string, url:string, method:string, extras:StringMap= {}): Promise<string>;
}
Run The Demo
git clone https://github.com/AxaFrance/oidc-client.git
cd oidc-client
# oidc client demo
cd /examples/oidc-client-demo
pnpm install
pnpm start
# then navigate to http://localhost:5174
How It Works
This component is a pure vanilla JS OIDC client library agnostic to any framework. It is a real alternative to existing oidc-client libraries.
More information about OIDC
- French : Augmentez la sécurité et la simplicité de votre Système d’Information OpenID Connect
- English : Increase the security and simplicity of your information system with openid connect
- English: youtube OIDC
- French: youtube OIDC
Hash route
@axa-fr/oidc-client
work also with hash route.
export const configurationIdentityServerWithHash = {
client_id: 'interactive.public.short',
redirect_uri: window.location.origin + '#authentication-callback',
silent_redirect_uri: window.location.origin + '#authentication-silent-callback',
scope: 'openid profile email api offline_access',
authority: 'https://demo.duendesoftware.com',
refresh_time_before_tokens_expiration_in_second: 70,
service_worker_relative_url: '/OidcServiceWorker.js',
service_worker_only: false,
};