npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@postinumero/react-router-oidc-client

v0.3.3

Published

A React Router integration for OpenID Connect (OIDC) authentication, built on top of oidc-client-ts.

Readme

@postinumero/react-router-oidc-client

A React Router integration for OpenID Connect (OIDC) authentication, built on top of oidc-client-ts.

Provides hooks and utilities for managing authentication in a React Router application, including protected routes, user authentication state management, and automatic login redirects.

  • 🔗 Keycloak support with role-based access controls
  • 📌 See the example for a complete implementation

Setup

1. Configure Authentication in root.tsx

import {
  loadOIDCRoot,
  useOIDC,
  useRevalidateUser,
} from "@postinumero/react-router-oidc-client";

// Load authentication state for the client
export const clientLoader = async (args: Route.ClientLoaderArgs) => {
  return {
    ...(await loadOIDCRoot(args)),
  };
};

// App Layout with authentication revalidation
export function Layout({ children }: PropsWithChildren) {
  useRevalidateUser(); // Ensure user state is kept up to date

  return <>{children}</>;
}

// Root component
export default function App() {
  useOIDC(); // Clean up logout parameters from URL, use refresh token if access token has expired.

  return <>{/* App content here */}</>;
}

2. Define Authentication Routes in routes.ts

Add authentication-related routes from the package.

import auth from "@postinumero/react-router-oidc-client/routes";

export default [
  // Other application routes
  ...auth, // Includes login & logout handlers
];

3. Handle Unauthorized Access with an Error Boundary

To handle 401 Unauthorized route errors, wrap root.tsx's ErrorBoundary with withHandleAuthErrorBoundary.

import {
  LoginForm,
  LoginRedirect,
  withHandleAuthErrorBoundary,
} from "@postinumero/react-router-oidc-client";
import { isRouteErrorResponse } from "react-router";

export const ErrorBoundary = withHandleAuthErrorBoundary(
  function UnauthorizedErrorBoundary(props) {
    // Redirect directly to external login flow with optional IDP hint:
    return (
      <LoginRedirect
        intent="redirect"
        {...{ "extraQueryParams.kc_idp_hint": "suomi-fi" }}
      />
    );

    // OR render a login form:
    return (
      <LoginForm>
        <input type="text" name="username" placeholder="Username" required />
        <input
          type="password"
          name="password"
          placeholder="Password"
          required
        />
        {isRouteErrorResponse(props.error) && props.error.data}
        <button>Login</button>
      </LoginForm>
    );

    // OR render a login link with optional IDP hint for external authentication:
    return (
      <LoginLink
        data={{
          intent: "redirect",
          "extraQueryParams.kc_idp_hint": "suomi-fi",
        }}
      >
        Login with Suomi.fi
      </LoginLink>
    );
  },
  function ErrorBoundary() {
    // Handle other errors
  },
);

Keycloak Integration

In root.tsx:

import {
  initKeycloak,
  loadOIDCRoot,
} from "@postinumero/react-router-oidc-client/keycloak";

initKeycloak({
  url: "https://example.com",
  realm: "example",
  client_id: "example-client",
});

export const clientLoader = async (args: Route.ClientLoaderArgs) => {
  return {
    ...(await loadOIDCRoot(args)),
  };
};

Server Side Rendering

1. Add server and client middlewares, add loader, add clientLoader.hydrate

In root.tsx:

import {
  loadOIDCRoot,
  oidc_ssr_clientMiddleware,
  oidc_ssr_middleware,
} from "@postinumero/react-router-oidc-client";

export const unstable_middleware = [oidc_ssr_middleware];
export const unstable_clientMiddleware = [oidc_ssr_clientMiddleware];

export const loader = async (args: Route.LoaderArgs) => {
  return {
    ...(await loadOIDCRoot(args)),
  };
};

export const clientLoader = async (args: Route.ClientLoaderArgs) => {
  return {
    ...(await loadOIDCRoot(args)),
  };
};

// For redirect login flow
clientLoader.hydrate = true;

2. Protect routes in loaders and actions

import { authenticated } from "@postinumero/react-router-oidc-client";

export const loader = async (args: Route.LoaderArgs) => {
  await authenticated(args);

  return null;
};

API Reference

Authentication Hooks & Utilities

getUser

Retrieve the current authenticated user.

import { getUser } from "@postinumero/react-router-oidc-client";

export const clientLoader = async () => {
  const user = await getUser();
  console.log(user?.access_token);
};

useUser

Retrieve the current authenticated user.

import { useUser } from "@postinumero/react-router-oidc-client";

function Component() {
  const user = useUser();
  // ...
}

<LoginForm>

import { LoginForm } from "@postinumero/react-router-oidc-client";

function Component() {
  return (
    <LoginForm>
      <input type="text" name="username" placeholder="Username" required />
      <input type="password" name="password" placeholder="Password" required />
      <button>Login</button>
    </LoginForm>
  );
}

Create a login form to use redirect flow with an optional IDP hint:

<LoginForm>
  <input type="hidden" name="extraQueryParams.kc_idp_hint" value="suomi-fi" />
  <button name="intent" value="redirect">
    Login with Suomi.fi
  </button>
</LoginForm>

<LoginLink>

Create a login link with optional IDP hint.

import { LoginLink } from "@postinumero/react-router-oidc-client";

function Component() {
  return (
    <LoginLink
      data={{ intent: "redirect", "extraQueryParams.kc_idp_hint": "suomi-fi" }}
    >
      Login with Suomi.fi
    </LoginLink>
  );
}

<LoginRedirect>

Redirect directly to the external login flow with an optional IDP hint.

import { LoginRedirect } from "@postinumero/react-router-oidc-client";

function Component() {
  return (
    <LoginRedirect
      intent="redirect"
      {...{ "extraQueryParams.kc_idp_hint": "suomi-fi" }}
    />
  );
}

<LogoutForm>

Handle user logout.

import { LogoutForm } from "@postinumero/react-router-oidc-client";

function Component() {
  return (
    <LogoutForm>
      <button>Logout</button>
    </LogoutForm>
  );
}

Perform silent logout, stay on current page even if current route is protected:

<LogoutForm redirect={location.href}>
  <button>Logout</button>
</LogoutForm>

Perform redirect logout, redirect to "/" if current route is protected:

<LogoutForm>
  <button name="intent" value="redirect">
    Logout
  </button>
</LogoutForm>

Perform redirect logout, stay on current page even if current route is protected:

<LogoutForm redirect={location.href}>
  <button name="intent" value="redirect">
    Logout
  </button>
</LogoutForm>

useUserEvent

Subscribe to UserManager events.

import { useUserEvent } from "@postinumero/react-router-oidc-client";

useUserEvent("unloaded", () => {
  console.log("You have been signed out.");
});

useLoginError

Access the raw login error inside an error boundary (e.g., after a failed login redirect).

import { useLoginError } from "@postinumero/react-router-oidc-client";

const loginError = useLoginError();

console.log(loginError?.error_description); // => "Invalid user credentials"

useLoginErrorMessage

Access a user-friendly, translated error message. Requires react-intl.

import { useLoginErrorMessage } from "@postinumero/react-router-oidc-client";

const loginErrorMessage = useLoginErrorMessage();

console.log(loginErrorMessage); // => "Invalid username or password"

<IsAuthenticated>

Conditionally render content based on authentication state.

import { IsAuthenticated } from "@postinumero/react-router-oidc-client";

function Component() {
  return (
    <IsAuthenticated fallback="Please log in">Welcome back!</IsAuthenticated>
  );
}

useIsAuthenticated

Check if a user is authenticated.

import { useIsAuthenticated } from "@postinumero/react-router-oidc-client";

function Component() {
  const isAuthenticated = useIsAuthenticated();
  // ...
}

Protected Route

Ensure a route is only accessible when authenticated.

import { authenticated } from "@postinumero/react-router-oidc-client";

export const clientLoader = async (args: Route.ClientLoaderArgs) => {
  const user = await authenticated(args);
  // ...
};

Keycloak-Specific API

getKeycloakUser

Retrieve the current authenticated Keycloak user.

import { getKeycloakUser } from "@postinumero/react-router-oidc-client/keycloak";

export const clientLoader = async () => {
  const user = await getKeycloakUser();
  // ...
};

useKeycloakUser

Retrieve the current authenticated Keycloak user.

import { useKeycloakUser } from "@postinumero/react-router-oidc-client/keycloak";

function Component() {
  const user = useKeycloakUser();
  // ...
}

<HasRole>, <HasRealmRole>, <HasResourceRole>

Render UI conditionally based on user roles.

import {
  HasRole,
  HasRealmRole,
  HasResourceRole,
} from "@postinumero/react-router-oidc-client/keycloak";

function Component() {
  return (
    <>
      <HasRole foo fallback={'User does not have role "foo"'} />
      <HasRole user viewer editor>
        User has roles "user", "editor", & "viewer"
      </HasRole>
      <HasRealmRole user viewer>
        User has realm roles "user" & "viewer"
      </HasRealmRole>
      <HasResourceRole example-client={["user", "editor"]}>
        User has "example-client" resource roles "user" & "editor"
      </HasResourceRole>
    </>
  );
}

useHasRole, useHasRealmRole, useHasResourceRole

import {
  useHasRole,
  useHasRealmRole,
  useHasResourceRole,
} from "@postinumero/react-router-oidc-client/keycloak";

export default function Component() {
  const hasFoo = useHasRole({ foo: true });
  const canView = useHasRealmRole({ user: true, viewer: true });
  const canEdit = useHasResourceRole({
    "example-client": ["user", "editor"],
  });
}

Keycloak Protected Route

Require specific roles for a protected route.

import { authenticated } from "@postinumero/react-router-oidc-client/keycloak";

export const clientLoader = async (args: Route.ClientLoaderArgs) => {
  const user = await authenticated(args, {
    realmRoles: ["viewer"],
    resourceRoles: { "example-client": ["user", "editor"] },
  });
  // ...
};