Openidconnect client for Single Page Applications
An OIDC client designed for Single Page Applications, typically Vite projects. With a streamlined API, you can easily integrate OIDC without needing to understand every detail of the protocol.
While oidc-client-ts
serves as a comprehensive toolkit, our library aims to provide a simplified, ready-to-use adapter that will pass
any security audit. We utilize oidc-client-ts
internally but abstract away most of its intricacies.
Our library takes a modular approach to OIDC and React, treating them as separate concerns that don't necessarily have to be intertwined. We offer an optional React adapter for added convenience, but it's not a requirement to use it and it's really trivial anyway.
$ yarn add oidc-spa
Create a silent-sso.html
file and put it in your public directory.
<htm
8000
l>
<body>
<script>
parent.postMessage(location.href, location.origin);
</script>
</body>
</html>
import { createOidc, decodeJwt } from "oidc-spa";
(async () => {
const oidc = await createOidc({
issuerUri: "https://auth.your-domain.net/auth/realms/myrealm",
clientId: "myclient",
// Optional, you can modify the url before redirection to the identity server
// Alternatively you can use: getExtraQueryParams: ()=> ({ ui_locales: "fr" })
transformUrlBeforeRedirect: url => `${url}&ui_locales=fr`
/**
* This parameter have to be provided if your App is not hosted at the origin of the subdomain.
* For example if your site is hosted by navigating to `https://www.example.com`
* you don't have to provide this parameter.
* On the other end if your site is hosted by navigating to `https://www.example.com/my-app`
* Then you want to set publicUrl to `/my-app`.
* If you are using Vite: `publicUrl: import.meta.env.BASE_URL`
* If you using Create React App: `publicUrl: process.env.PUBLIC_URL`
*
* Be mindful that `${window.location.origin}${publicUrl}/silent-sso.html` must return the `silent-sso.html` that
* you are supposed to have created in your `public/` directory.
*/
//publicUrl: `${window.location.origin}/my-app`
});
if (!oidc.isUserLoggedIn) {
// This return a promise that never resolve. Your user will be redirected to the identity server.
oidc.login({
// doesCurrentHrefRequiresAuth determines the behavior when a user gives up on loggin in and navigate back.
// We don't want to send him back to a authenticated route.
// If you are calling login because the user clicked
// on a 'login' button you should set doesCurrentHrefRequiresAuth to false.
// When you are calling login because your user navigated to a path that require authentication
// you should set doesCurrentHrefRequiresAuth to true
doesCurrentHrefRequiresAuth: false
//Optionally you can add some extra parameter to be added on the login url.
//extraQueryParams: { kc_idp_hint: "google" }
});
} else {
const {
// The accessToken is what you'll use as a Bearer token to authenticate to your APIs
accessToken,
// You can parse the idToken as a JWT to get some information about the user.
idToken
} = oidc.getTokens();
const user = decodeJwt(idToken) as {
// Use https://jwt.io/ to tell what's in your idToken
sub: string;
preferred_username: string;
};
console.log(`Hello ${user.preferred_username}`);
// To call when the user click on logout.
// You can also redirect to a custom url with { redirectTo: "specific url", url: `${location.origin}/bye` }
oidc.logout({ redirectTo: "home" });
}
})();
import { createOidcProvider, useOidc } from "oidc-spa/react";
import { decodeJwt } from "oidc-spa";
const { OidcProvider } = createOidcProvider({
issuerUri: "https://auth.your-domain.net/auth/realms/myrealm",
clientId: "myclient"
// See above for other parameters
});
ReactDOM.render(
<OidcProvider
// Optional, it's usually so fast that a fallback is really not required.
fallback={<>Logging you in...</>}
>
<App />
</OidcProvider>,
document.getElementById("root")
);
function App() {
const { oidc } = useOidc();
if (!oidc.isUserLoggedIn) {
return (
<>
You're not logged in.
<button
onClick={() =>
oidc.login({
doesCurrentHrefRequiresAuth: false
//Optionally you can add some extra parameter to be added on the login url.
//extraQueryParams: { kc_idp_hint: "google" }
})
}
>
Login
</button>
</>
);
}
return (
<AppLoggedIn
// You can also redirect to a custom url with { redirectTo: "specific url", url: `${location.origin}/bye` }
logout={() => oidc.logout({ redirectTo: "home" })}
/>
);
}
function AppLoggedIn(props: { logout: () => Promise<never> }) {
const { logout } = props;
const { user } = useUser();
return (
<>
<h1>Hello {user.preferred_username}</h1>
<button onClick={logout}>Log out</button>
</>
);
}
// Convenience hook to get the parsed idToken
// To call only when the user is logged in
function useUser() {
const { oidc } = useOidc();
if (!oidc.isUserLoggedIn) {
throw new Error("This hook should be used only on authenticated routes");
}
// NOTE: When idToken changes, the component get re-rendered
// so idToken can be used in dependency arrays. ✅
const { idToken } = oidc.getTokens();
const user = useMemo(
() =>
decodeJwt(idToken) as {
// Use https://jwt.io/ to tell what's in your idToken
sub: string;
preferred_username: string;
},
[idToken]
);
return { user };
}
The token refresh is handled automatically for you, however you can manually trigger
a token refresh with oidc.getTokens()
.
If the OIDC server is down or misconfigured an error get printed in the console, everything
continues as normal with the user unauthenticated. If the user tries to login an alert saying
that authentication is not available at the moment is displayed and nothing happens.
This enable your the part of your app that do not requires authentication to remain up even when
your identities server is facing issues.
A very basic setup with Keycloak and Create React App.
- It's live here: https://starter.keycloakify.dev/
- The source code is here.
The setup is collocated with a custom Keycloak theme. The part where we use
oidc-spa
is here.
This library is powers the authentication of the following platforms:
You have made some changes to the code and you want to test them in your app before submitting a pull request?
Assuming you/my-app
have oidc-spa
as a dependency.
cd ~/github
git clone https://github.com/you/my-app
cd my-app
yarn
cd ~/github
git clone https://github.com/garronej/oidc-spa
cd oidc-spa
yarn
yarn build
yarn link-in-app my-app
npx tsc -w
# Open another terminal
cd ~/github/my-app
rm -rf node_modules/.cache
yarn start # Or whatever my-app is using for starting the project
You don't have to use ~/github
as reference path. Just make sure my-app
and oidc-spa
are in the same directory.
Note for the maintainer: You might run into issues if you do not list all your singleton dependencies in
src/link-in-app.js -> singletonDependencies
. A singleton dependency is a dependency that can only be present once in an App. Singleton dependencies are usually listed as peerDependencies examplereact
,@emotion/*
.