@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.
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
node ./node_modules/@axa-fr/oidc-client/bin/copy-service-worker-files.mjs public
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".
const trustedDomains = {
default: {
oidcDomains :["https://demo.duendesoftware.com"],
accessTokenDomains : ["https://www.myapi.com/users"]
},
};
trustedDomains.config_show_access_token = {
oidcDomains :["https://demo.duendesoftware.com"],
accessTokenDomains : ["https://www.myapi.com/users"],
showAccessToken: false,
};
trustedDomains.config_with_dpop = {
domains: ["https://demo.duendesoftware.com"],
demonstratingProofOfPossession: true,
demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent: true,
};
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',
service_worker_only: false,
demonstrating_proof_of_possession: false,
};
const href = window.location.href;
const oidcClient = OidcClient.getOrCreate()(configuration);
const oidcFetch = oidcClient.fetchWithTokens(fetch);
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) {
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 {
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,
redirect_uri: String.isRequired,
silent_redirect_uri: String,
silent_login_uri: String,
silent_login_timeout: Number,
scope: String.isRequired,
authority: String.isRequired,
storage: Storage,
authority_configuration: {
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,
service_worker_relative_url: String,
service_worker_keep_alive_path: String,
service_worker_only: Boolean,
service_worker_activate: () => boolean,
service_worker_update_require_callback: (registration:any, stopKeepAlive:Function) => Promise<void>,
service_worker_register: (url: string) => Promise<ServiceWorkerRegistration>,
extras: StringMap | undefined,
token_request_extras: StringMap | undefined,
authority_time_cache_wellknowurl_in_second: 60 * 60,
authority_timeout_wellknowurl_in_millisecond: 10000,
monitor_session: Boolean,
token_renew_mode: String,
token_automatic_renew_mode: TokenAutomaticRenewMode.AutomaticOnlyWhenFetchExecuted,
logout_tokens_to_invalidate: Array<string>,
location: ILOidcLocation,
demonstrating_proof_of_possession: Boolean,
demonstrating_proof_of_possession_configuration: DemonstratingProofOfPossessionConfiguration
};
interface DemonstratingProofOfPossessionConfiguration {
generateKeyAlgorithm: RsaHashedKeyGenParams | EcKeyGenParams,
digestAlgorithm: AlgorithmIdentifier,
importKeyAlgorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm,
signAlgorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams,
jwtHeaderAlgorithm: string
};
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
export class OidcClient {
constructor(oidc: Oidc);
subscribeEvents(func: EventSubscriber): string;
removeEventSubscription(id: string): void;
publishEvent(eventName: string, data: any): void;
static getOrCreate(getFetch: () => Fetch)(configuration: OidcConfiguration, name?: string): OidcClient;
static get(name?: string): OidcClient;
static eventNames: Oidc.eventNames;
tryKeepExistingSessionAsync(): Promise<boolean>;
loginAsync(callbackPath?: string, extras?: StringMap, isSilentSignin?: boolean, scope?: string, silentLoginOnly?: boolean): Promise<unknown>;
logoutAsync(callbackPathOrUrl?: string | null | undefined, extras?: StringMap): Promise<void>;
silentLoginCallbackAsync(): Promise<void>;
renewTokensAsync(extras?: StringMap): Promise<void>;
loginCallbackAsync(): Promise<LoginCallback>;
get tokens(): Tokens;
get configuration(): OidcConfiguration;
async getValidTokenAsync(waitMs = 200, numberWait = 50): Promise<ValidToken>;
fetchWithTokens(fetch: Fetch, demonstrating_proof_of_possession=false): Fetch;
async userInfoAsync<T extends OidcUserInfo = OidcUserInfo>(noCache = false, demonstrating_proof_of_possession=false): Promise<T>;
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
cd /examples/oidc-client-demo
pnpm install
pnpm start
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
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,
};