Safeguard
Open Source

react-oidc-context: A Security Guide

react-oidc-context wraps oidc-client-ts in React hooks for SPA authentication. Here is how to wire it up without leaking tokens or trusting the wrong callback.

Yukti Singhal
Platform Engineer
6 min read

react-oidc-context is a lightweight React authentication library that wraps oidc-client-ts with hooks and a context provider, and using it securely comes down to enforcing PKCE, keeping tokens out of persistent storage, and validating every callback. It is a thin, well-scoped wrapper: it does not implement the OpenID Connect protocol itself, it delegates that to oidc-client-ts and gives you an idiomatic React surface on top.

At the time of writing the package is actively maintained, with version 3.3.1 published a few months back and roughly 90,000 weekly downloads on npm. That health matters, because an auth library that goes stale is a liability, but a maintained one still has to be configured correctly to be safe.

What the library gives you

The core of the API is a provider and a hook. You wrap your app in AuthProvider with your identity provider's settings, and any component calls useAuth() to read the authentication state or trigger sign-in and sign-out.

import { AuthProvider, useAuth } from "react-oidc-context";

const oidcConfig = {
  authority: "https://idp.example.com/",
  client_id: "your-spa-client-id",
  redirect_uri: window.location.origin + "/callback",
  response_type: "code",
  scope: "openid profile email",
};

function App() {
  return (
    <AuthProvider {...oidcConfig}>
      <Dashboard />
    </AuthProvider>
  );
}

function Dashboard() {
  const auth = useAuth();
  if (auth.isLoading) return <p>Loading...</p>;
  if (!auth.isAuthenticated) return <button onClick={() => auth.signinRedirect()}>Log in</button>;
  return <p>Signed in as {auth.user?.profile.email}</p>;
}

That is the whole happy path. The security work is in the config object and in what you do with the tokens after.

Always use the authorization code flow with PKCE

For a single-page app there is exactly one correct flow: authorization code with PKCE. Set response_type: "code" as shown above. The older implicit flow, which returned tokens directly in the URL fragment, is deprecated for public clients precisely because those tokens landed in browser history and Referer headers.

oidc-client-ts enables PKCE automatically for the code flow, so you mostly need to avoid overriding it. Do not set a client secret in a SPA config. A browser cannot keep a secret, so any secret you embed is public. Register the client as a public client at the identity provider and rely on PKCE for the proof.

Token storage is the real decision

By default oidc-client-ts stores the user session in the browser's session storage. This is the sharpest security trade-off in the whole setup. Anything readable by JavaScript is readable by any cross-site scripting payload that lands on your page. Session storage clears when the tab closes, which limits exposure compared to local storage, but it does not remove the risk.

Two mitigations matter. First, keep access-token lifetimes short so a stolen token expires quickly. Second, invest heavily in preventing cross-site scripting in the first place, because in a SPA that is the attack that steals your tokens. A strict Content Security Policy and disciplined output handling do more for this library's safety than any of its own options. For the broader picture of protecting the session that follows login, see the user session management guide.

If your threat model cannot tolerate JavaScript-accessible tokens at all, the answer is a backend-for-frontend that holds the tokens server-side in an HttpOnly cookie and proxies API calls. That moves you outside what react-oidc-context does on its own, but it is the strongest option.

Silent renew and the callback route

Long-lived apps need to refresh tokens without bouncing the user through a full login. Enable automatic silent renew:

const oidcConfig = {
  // ...as above
  automaticSilentRenew: true,
  onSigninCallback: () => {
    // strip the code and state from the URL after processing
    window.history.replaceState({}, document.title, window.location.pathname);
  },
};

The onSigninCallback cleanup is easy to forget and worth doing. After the identity provider redirects back with the authorization code in the query string, you do not want that code lingering in the address bar or in shared links. Replacing the history entry removes it.

Register your redirect URI exactly, including scheme and path, at the identity provider, and register only the origins you actually use. A wildcard or overly broad redirect URI is one of the classic OIDC misconfigurations, because it lets an attacker redirect the authorization response to a page they control.

Validate the provider, not just the happy path

The library and oidc-client-ts validate the ID token signature and issuer for you when the authority is configured correctly, but you own two checks. Confirm the authority URL is HTTPS and points at the real provider, and make sure your identity provider's discovery document is served over TLS. If either is spoofable, the whole trust chain collapses. Never disable issuer or signature validation to work around a certificate warning in development; fix the certificate instead.

Keeping the dependency healthy

Because this package sits directly on the authentication path, treat its supply chain seriously. Pin the version, watch oidc-client-ts (the transitive dependency doing the cryptographic heavy lifting) as closely as the wrapper, and let a scanner tell you when either publishes a security release. An SCA tool such as Safeguard can flag a vulnerable version transitively so you are not manually tracking two packages. Beyond that, review the release notes before upgrading, since auth libraries occasionally change default behavior in ways that affect security.

FAQ

Is react-oidc-context safe to use in production?

Yes, when configured with the authorization code flow, PKCE, short token lifetimes, and strong cross-site scripting defenses. The library itself is a maintained, thin wrapper; the risks that remain are the general risks of doing OIDC in a browser, not defects unique to it.

Where does react-oidc-context store tokens?

Through oidc-client-ts it uses the browser's session storage by default, which is accessible to JavaScript on the page. For higher assurance, move to a backend-for-frontend pattern that keeps tokens in an HttpOnly cookie server-side.

Do I need a client secret with react-oidc-context?

No. A SPA is a public client and cannot keep a secret. Register it as public at your identity provider and rely on PKCE. Any secret embedded in browser code is effectively published.

How do I handle token refresh?

Set automaticSilentRenew: true so the library refreshes tokens in the background before they expire. Keep access tokens short-lived so a leaked one has a small window, and rely on the provider's refresh mechanism rather than extending token lifetimes.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.