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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ricardoqmd/auth-keycloak

v1.1.0

Published

Keycloak adapter for @ricardoqmd/auth-core — implements the AuthProvider contract using keycloak-js

Readme

@ricardoqmd/auth-keycloak

Keycloak adapter for @ricardoqmd/auth-core. Wraps keycloak-js to implement the AuthProvider contract.

Install

npm install @ricardoqmd/auth-keycloak keycloak-js @ricardoqmd/auth-core

keycloak-js is a peer dependency — install it explicitly so you control its version.

What's in the box

  • createKeycloakProvider() — factory that returns a KeycloakProvider (a superset of AuthProvider<KeycloakIdpClaims>) ready to plug into a framework binding. Its bound accessors hasClientRole() / clientRoles() check client roles against the configured clientId.
  • KeycloakProvider / KeycloakIdpClaims — TypeScript interfaces for the returned provider and the Keycloak-specific token claims (realm_access, resource_access).
  • hasResourceRole() / resourceRoles() — standalone predicate and extractor for client-level (resource) roles on an arbitrary client. Use these when the universal hasRole() from your binding is not enough. See Roles.

Quick start

If you are using Next.js, see @ricardoqmd/auth-nextjs for end-to-end setup. The snippet below shows the adapter in isolation:

import { createKeycloakProvider } from "@ricardoqmd/auth-keycloak";

const provider = createKeycloakProvider({
  config: {
    url: "https://keycloak.example.com",
    realm: "my-realm",
    clientId: "my-app",
  },
});

// `provider` implements AuthProvider<KeycloakIdpClaims>
// Pass it to <AuthProvider> from @ricardoqmd/auth-nextjs (or any future binding)

Configuration options

createKeycloakProvider({
  config: {
    url: string;          // Keycloak server URL
    realm: string;        // realm name
    clientId: string;     // OAuth client ID
  },
  onLoad?: "login-required" | "check-sso";  // default: "login-required"
  checkLoginIframe?: boolean;                // default: false
  pkceMethod?: "S256";                       // default: "S256"
  responseMode?: "fragment" | "query";      // default: keycloak-js default ("fragment")
  silentCheckSsoRedirectUri?: string;
  logoutRedirectUri?: string;
});

Roles

Keycloak has two kinds of roles, and the distinction is the single most common source of "why does my role check return false?" (ADR-018).

hasRole() / hasAnyRole() are realm roles only

The universal hasRole() / hasAnyRole() on your binding's useAuth() read user.roles, which this adapter maps from realm_access only. They live on the AuthHandle in @ricardoqmd/auth-core, which is IdP-agnostic and frozen (ADR-004, ADR-009): "realm" and "resource_access" are Keycloak vocabulary and cannot leak into the core contract. So their meaning is fixed — realm roles — and cannot be widened.

The trap: the functional role is usually a client role

In a department-wide SSO (one realm, many apps), realm roles are typically global access markers (kronia-app), while the functional role inside an app is granted per client and lives in resource_access[clientId].roles (administrador, capturista). That means:

const { hasRole } = useAuth();
hasRole("capturista"); // → false, SILENTLY — "capturista" is a *client* role

No error is thrown; the check just answers false. If your app's roles are client roles, use the accessors below instead.

Client-role accessors

createKeycloakProvider() returns a KeycloakProvider — a superset of the AuthProvider port — with two accessors bound to this provider's own config.clientId, so you never repeat the client id and can't drift onto the wrong client:

provider.hasClientRole(claims, "capturista"); // boolean, on THIS client
provider.clientRoles(claims);                 // string[] of roles on THIS client

For a different client, use the standalone utilities (they take the resource id explicitly):

import { hasResourceRole, resourceRoles } from "@ricardoqmd/auth-keycloak";

hasResourceRole(claims, "billing-app", "auditor"); // boolean, on another client
resourceRoles(claims, "billing-app");              // string[] on another client

Both accessors and utilities take claims as an argument rather than reading the Keycloak instance internally. That is deliberate: reading kc.tokenParsed inside the provider is an untracked read, so a computed / useMemo would not re-evaluate when the token refreshes and a revoked role would keep rendering. Passing the reactive idpClaims makes the dependency explicit.

Reactive (Vue computed):

<script setup lang="ts">
import { computed } from "vue";
import { useAuth } from "@ricardoqmd/auth-vue";
import type { KeycloakIdpClaims } from "@ricardoqmd/auth-keycloak";
import { provider } from "./auth";

const { idpClaims } = useAuth<KeycloakIdpClaims>();
const canCapture = computed(() => provider.hasClientRole(idpClaims.value, "capturista"));
</script>

<template>
  <button v-if="canCapture">Capturar</button>
</template>

Imperative (route guard / interceptor):

router.beforeEach((to) => {
  if (to.meta.requiresCapture && !provider.hasClientRole(auth.getIdpClaims(), "capturista")) {
    return { name: "forbidden" };
  }
  return true;
});

Need the list — to display roles, or to assemble subjectAttributes.roles for a policy decision point? Use the extractor: provider.clientRoles(claims) (bound) or resourceRoles(claims, resource) (any client).

Freshness and enforcement

Since ADR-017, claims are re-normalized on every token refresh, so role changes propagate within the token lifecycle — a revoked role disappears at the next refresh without a full page reload (bounded by the access token's remaining lifetime, not immediate).

SPA role checks are advisory, never an enforcement boundary. The SPA is a public client and its JavaScript is user-modifiable; these checks make the UI accurate (stop offering actions the backend will reject), not secure. Enforcement is the policy enforcement point (PEP) validating the token server-side.

Deployment notes

  • Secure context required. keycloak-js uses the Web Crypto API for PKCE (S256), which browsers expose only in a secure context — HTTPS or localhost. Served over plain http://<ip>, init fails with INIT_FAILED ("Web Crypto API is not available").
  • HTML5-history routers and responseMode. keycloak-js defaults to responseMode: "fragment", so the OIDC callback returns in the URL fragment (#state=...&code=...). Apps using an HTML5-history router (e.g. vue-router's createWebHistory) don't manage the fragment, so it lingers in the address bar after login. Set responseMode: "query" to receive the callback as ?code=... instead — both keycloak-js and HTML5 routers strip query params from the URL reliably. Default behavior is unchanged when the option is omitted.
  • Silent check-sso and CSP. Setting silentCheckSsoRedirectUri loads Keycloak in a hidden iframe; Keycloak's default frame-ancestors 'self' CSP blocks that cross-origin until you allow the app's origin (Realm Settings → Security Defenses → Content-Security-Policy). Redirect-based flows (login-required, or check-sso without silentCheckSsoRedirectUri) avoid the iframe entirely.

Compatibility

| Package version | keycloak-js | Keycloak server | | --------------- | ------------- | --------------- | | 1.x | >=26.0 <28.0 | >=26.0 |

Since Keycloak 26.2, the keycloak-js adapter is released independently from the server and is backwards compatible with all actively supported Keycloak server versions.

Status

Stable. The public API is frozen and follows SemVer from 1.0 onward (see ADR-009): additive changes are non-breaking; removing or renaming an export is a major bump.

License

MIT © ricardoqmd