Remix Auth Keycloak Strategy
remix-keycloak is an authentication strategy for Remix applications, specifically designed to integrate with Keycloak identity and access management. It extends the `remix-auth` library's `OAuth2Strategy` to provide a robust solution for authenticating users via a Keycloak instance. Maintained by Cybernite Intelligence, this package serves as a direct successor and active continuation of the now-archived `remix-auth-keycloak`. The current stable version is 2.0.4. While there isn't a fixed release cadence, updates are made as needed to ensure compatibility with Remix v1 and v2, and to address Keycloak-related changes. A key differentiator is its commitment to supporting multiple runtimes, including Node.js, Cloudflare Workers, and Netlify functions, making it versatile for various deployment environments. It simplifies the setup of Keycloak-based authentication flows, including client ID/secret handling, callback URLs, and token management within a Remix context.
Common errors
-
Error: Response not found for strategy "keycloak"
cause This usually indicates that the `authenticator.authenticate` call in your Remix action or loader is not correctly configured or the strategy name ('keycloak' by default) doesn't match.fixEnsure that `authenticator.use(keycloakStrategy, "keycloak")` is called with the correct strategy name and that the `authenticator.authenticate("keycloak", request, ...)` calls in your routes also use `"keycloak"`. -
TypeError: Cannot read properties of undefined (reading 'authenticate')
cause The `authenticator` instance was not properly initialized or exported/imported in the file where it's being used.fixVerify that `export const authenticator = new Authenticator<User>(sessionStorage);` is in a server-side utility file (e.g., `app/utils/auth.server.ts`) and is correctly imported into your route files. -
Keycloak: Invalid client or redirect URI
cause This error originates from the Keycloak server and means there's a mismatch in the `clientID`, `clientSecret`, or `callbackURL` provided by your Remix application and what Keycloak expects.fixDouble-check the `clientID`, `clientSecret`, and `callbackURL` in your `KeycloakStrategy` configuration against your Keycloak client settings. Pay close attention to trailing slashes and case sensitivity for the URL.
Warnings
- breaking This package is a direct replacement for `remix-auth-keycloak`, which was archived on December 2nd, 2023. Users migrating from the old package must update their `package.json` and all import statements from `remix-auth-keycloak` to `remix-keycloak`.
- gotcha The `KeycloakStrategy` requires several configuration options (domain, realm, clientID, clientSecret, callbackURL) that are critical for connecting to your Keycloak instance. Incorrect values will lead to authentication failures.
- gotcha For production deployments, `useSSL` should almost always be `true`. Misconfiguring SSL can lead to security vulnerabilities or connection issues, especially when Keycloak is served over HTTPS.
- gotcha The authentication `callbackURL` configured in `KeycloakStrategy` must exactly match one of the valid redirect URIs configured for your client in the Keycloak admin console. A mismatch will result in a redirect error from Keycloak.
Install
-
npm install remix-keycloak -
yarn add remix-keycloak -
pnpm add remix-keycloak
Imports
- KeycloakStrategy
import { Keycloak } from 'remix-auth-keycloak'import { KeycloakStrategy } from 'remix-keycloak' - Authenticator
import { Authenticator } from 'remix-auth' - loader
export let loader: LoaderFunction = ({ request }) => { /* ... */ } - action
export let action: ActionFunction = ({ request }) => { /* ... */ }
Quickstart
import { Authenticator } from "remix-auth";
import { KeycloakStrategy } from "remix-keycloak";
import { createCookieSessionStorage } from "@remix-run/node";
interface User {
id: string;
email: string;
name: string;
}
// Create a session storage for the authenticator
const sessionStorage = createCookieSessionStorage({
cookie: {
name: "_session",
sameSite: "lax",
path: "/",
httpOnly: true,
secrets: [process.env.SESSION_SECRET ?? 'super-secret-key'],
secure: process.env.NODE_ENV === "production",
},
});
export const authenticator = new Authenticator<User>(sessionStorage);
const keycloakStrategy = new KeycloakStrategy(
{
useSSL: process.env.KEYCLOAK_USE_SSL === 'true',
domain: process.env.KEYCLOAK_DOMAIN ?? 'your-keycloak-domain.com',
realm: process.env.KEYCLOAK_REALM ?? 'your-realm',
clientID: process.env.KEYCLOAK_CLIENT_ID ?? 'your-client-id',
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET ?? 'your-client-secret',
callbackURL: process.env.KEYCLOAK_CALLBACK_URL ?? 'http://localhost:3000/auth/keycloak/callback',
},
async ({ accessToken, refreshToken, extraParams, profile }) => {
// In a real application, you would typically find or create a user in your DB
// based on the profile data from Keycloak.
console.log("Keycloak Profile:", profile);
console.log("Access Token:", accessToken);
// For this example, we'll return a mock user
return {
id: profile.id || profile.emails?.[0]?.value || 'anonymous',
email: profile.emails?.[0]?.value || 'user@example.com',
name: profile.displayName || 'Keycloak User'
};
}
);
authenticator.use(keycloakStrategy, "keycloak");
// Example route: app/routes/login.tsx
// export default function Login() {
// return (
// <form action="/auth/keycloak" method="post">
// <button>Login with Keycloak</button>
// </form>
// );
// }
// Example route: app/routes/auth/keycloak.tsx
// import type { ActionFunction } from "@remix-run/node";
// import { authenticator } from "~/utils/auth.server"; // Adjust path as needed
// export let action: ActionFunction = ({ request }) => {
// return authenticator.authenticate("keycloak", request);
// };
// Example route: app/routes/auth/keycloak/callback.tsx
// import type { LoaderFunction } from "@remix-run/node";
// import { redirect } from "@remix-run/node";
// import { authenticator } from "~/utils/auth.server"; // Adjust path as needed
// export let loader: LoaderFunction = ({ request }) => {
// return authenticator.authenticate("keycloak", request, {
// successRedirect: "/dashboard",
// failureRedirect: "/login",
// });
// };