Basic Usage
Let's get your App authenticated!
Last updated
Was this helpful?
Let's get your App authenticated!
Last updated
Was this helpful?
Before getting started, you need to get a hold of the few parameters required to connect to your OIDC provider. Find instruction on how to configure your OIDC provider on the following documentation page:
import { createOidc } from "oidc-spa";
import { z } from "zod";
const oidc = await createOidc({
issuerUri: "https://auth.your-domain.net/realms/myrealm",
clientId: "myclient",
/**
* Vite: `homeUrl: import.meta.env.BASE_URL`
* CRA: `homeUrl: process.env.PUBLIC_URL`
* Other: `homeUrl: "/"` (Usually, can be something like "/dashboard")
*/
homeUrl: import.meta.env.BASE_URL,
//scopes: ["profile", "email", "api://my-app/access_as_user"],
extraQueryParams: () => ({
ui_locales: "en" // Keycloak login/register page language
//audience: "https://my-app.my-company.com/api"
}),
decodedIdTokenSchema: z.object({
preferred_username: z.string(),
name: z.string()
//email: z.string().email().optional()
})
});
if (!oidc.isUserLoggedIn) {
// The user is not logged in.
// We can call login() to redirect the user to the login/register page.
// This return a promise that never resolve.
oidc.login({
/**
* If you are calling login() in the callback of a click event
* set this to false.
*/
doesCurrentHrefRequiresAuth: false
/**
* Optionally, you can add some extra parameter
* to be added on the login url.
* (Can also be a parameter of createOidc `extraQueryParams: ()=> ({ ui_locales: "fr" })`)
*/
//extraQueryParams: { kc_idp_hint: "google", ui_locales: "fr" }
/**
* You can allso set where to redirect the user after
* successful login
*/
// redirectUrl: "/dashboard"
/**
* Keycloak: You can also send the users directly to the register page
* see: https://github.com/keycloakify/oidc-spa/blob/14a3777601c50fa69d1221495d77668e97443119/examples/tanstack-router-file-based/src/components/Header.tsx#L54-L66
*/
});
} else {
// The user is logged in.
const {
// The accessToken is what you'll use as a Bearer token to
// authenticate to your APIs
accessToken
} = await oidc.getTokens_next();
fetch("https://api.your-domain.net/orders", {
headers: {
Authorization: `Bearer ${accessToken}`
}
})
.then(response => response.json())
.then(orders => console.log(orders));
// To call when the user click on logout.
// You can also redirect to a custom url with
// { redirectTo: "specific url", url: "/bye" }
oidc.logout({ redirectTo: "home" });
const decodedIdToken = oidc.getDecodedIdToken();
console.log(`Hello ${decodedIdToken.preferred_username}`);
}
src/
├── components/
│ └── Header.tsx
├── pages/
│ ├── Home.tsx
│ ├── Account.tsx
│ ├── Orders.tsx
├── App.tsx
├── oidc.tsx
└── main.tsx
import { createReactOidc } from "oidc-spa/react";
import { z } from "zod";
export const { OidcProvider, useOidc, getOidc, withLoginEnforced } =
createReactOidc(async () => ({
issuerUri: "https://auth.your-domain.net/realms/myrealm",
clientId: "myclient",
/**
* Vite: `homeUrl: import.meta.env.BASE_URL`
* CRA: `homeUrl: process.env.PUBLIC_URL`
* Other: `homeUrl: "/"` (Usually, can be something like "/dashboard")
*/
homeUrl: import.meta.env.BASE_URL,
//scopes: ["profile", "email", "api://my-app/access_as_user"],
extraQueryParams: () => ({
ui_locales: "en" // Keycloak login/register page language
//audience: "https://my-app.my-company.com/api"
}),
decodedIdTokenSchema: z.object({
preferred_username: z.string(),
name: z.string()
//email: z.string().email().optional()
})
}));
export const fetchWithAuth: typeof fetch = async (
input,
init
) => {
const oidc = await getOidc();
if (oidc.isUserLoggedIn) {
const { accessToken } = await oidc.getTokens();
(init ??= {}).headers = {
...init.headers,
Authorization: `Bearer ${accessToken}`
};
}
return fetch(input, init);
};
import ReactDOM from "react-dom/client";
import { OidcProvider } from "./oidc";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")!).render(
<OidcProvider
//fallback={<h1>Checking authentication ⌛️</h1>}
>
<App />
</OidcProvider>
);
import { Suspense, lazy } from "react";
import Header from "./components/Header";
const HomePage = lazy(() => import("./pages/Home"));
const OrderPage = lazy(() => import("./pages/Orders"));
const AccountPage = lazy(() => import("./pages/Account"));
export default function App() {
const route = useRoute();
return (
<>
<Header />
<main>
<Suspense>
{route === "home" && <HomePage />}
{route === "orders" && <OrderPage />}
{route === "account" && <AccountPage />}
</Suspense>
</main>
</>
);
}
import { useOidc } from "../oidc";
export default function Header() {
const { isUserLoggedIn } = useOidc();
return (
<header>
<nav>
<Link to="home">Home</Link>
<Link to="orders">Orders</Link>
</nav>
{isUserLoggedIn ? (
<AuthButtonsLoggedIn />
) : (
<AuthButtonsNotLoggedIn />
)}
</header>
);
}
function AuthButtonsLoggedIn() {
const { decodedIdToken, logout } = useOidc({ assert: "user logged in" });
return (
<div>
<span>Logged in as {decodedIdToken.preferred_username}</span>
<Link to="account">Account</Link>
<button onClick={() => logout({ redirectTo: "home" })}>
Logout
</button>
</div>
);
}
function AuthButtonsNotLoggedIn() {
const { login } = useOidc({ assert: "user not logged in" });
return (
<div>
<button onClick={() => login()}>Login</button>
<button
onClick={() =>
login({
// Keycloak:
transformUrlBeforeRedirect: url => {
const urlObj = new URL(url);
urlObj.pathname = urlObj.pathname.replace(
/\/auth$/,
"/registrations"
);
return urlObj.href;
}
// Auth0:
// extraQueryParams: { screen_hint: "signup" }
})
}
>
Register
</button>
</div>
);
}
import { useOidc } from "../oidc";
export default function Page() {
const { isUserLoggedIn, decodedIdToken } = useOidc();
return (
<h1>Welcome {isUserLoggedIn ? decodedIdToken.name : "stranger"}!</h1>
);
}
import { useEffect, useState } from "react";
import { withLoginEnforced, fetchWithAuth } from "../oidc";
type Order = {
id: number;
name: string;
};
// If this component is mounted and the user is not logged in
// the user will be redirected to the login.
const Page = withLoginEnforced(() => {
const [orders, setOrders] = useState<Order[] | undefined>(undefined);
useEffect(() => {
fetchWithAuth("https://api.your-domain.net/orders", {
headers: {
"Content-Type": "application/json"
}
})
.then(response => response.json())
.then(orders => setOrders(orders));
}, []);
if (orders === undefined) {
return <>Loading orders ⌛️</>;
}
return (
<ul>
{orders.map(order => (
<li key={order.id}>{order.name}</li>
))}
</ul>
);
});
export default Page;
import { useOidc, withLoginEnforced } from "../oidc";
import { parseKeycloakIssuerUri } from "oidc-spa/tools/parseKeycloakIssuerUri";
const Page = withLoginEnforced(() => {
const {
goToAuthServer,
backFromAuthServer,
params: { issuerUri, clientId }
} = useOidc({ assert: "user logged in" });
const keycloak = parseKeycloakIssuerUri(issuerUri);
if (keycloak === undefined) {
throw new Error(
"We expect Keycloak to be the OIDC provider of this App"
);
}
return (
<div>
<h1>Account</h1>
<p>
<a
href={keycloak.getAccountUrl({
clientId,
backToAppFromAccountUrl: location.href,
locale: "en"
})}
>
Go to Keycloak Account Management Page
</a>
</p>
<p>
<button
onClick={() =>
goToAuthServer({
extraQueryParams: {
kc_action: "CHANGE_PASSWORD"
}
})
}
>
Change My Password
</button>
{backFromAuthServer?.extraQueryParams.kc_action ===
"CHANGE_PASSWORD" && (
<span>
{backFromAuthServer.result.kc_action_status ===
"success"
? "Password Updated!"
: "Password unchanged"}
</span>
)}
</p>
<p>
<button
onClick={() =>
goToAuthServer({
extraQueryParams: {
kc_action: "UPDATE_PROFILE"
}
})
}
>
Update My Profile Information
</button>
{backFromAuthServer?.extraQueryParams.kc_action ===
"UPDATE_PROFILE" && (
<span>
{backFromAuthServer.result.kc_action_status ===
"success"
? "Profile Updated!"
: "Profile unchanged"}
</span>
)}
</p>
<p>
<button
onClick={() =>
goToAuthServer({
extraQueryParams: {
kc_action: "delete_account"
}
})
}
>
Delete My Account
</button>
</p>
</div>
);
});
export default Page;
Testable examples: