CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-keycloak-js

JavaScript adapter for Keycloak providing OpenID Connect authentication and UMA authorization for web applications.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

authorization.mddocs/

Authorization and Policy Enforcement

User-Managed Access (UMA) and policy enforcement capabilities providing fine-grained authorization for applications protected by Keycloak policy enforcers.

Core Imports

import Keycloak from "keycloak-js";
import KeycloakAuthorization from "keycloak-js/keycloak-authz";

// Create Keycloak instance first
const keycloak = new Keycloak({
  url: "http://keycloak-server",
  realm: "my-realm",
  clientId: "my-app"
});

// Create authorization instance
const authorization = new KeycloakAuthorization(keycloak);

// Initialize Keycloak before using authorization
await keycloak.init();

Capabilities

KeycloakAuthorization Constructor

Creates an authorization client instance for UMA and policy enforcement.

/**
 * Creates an authorization client for UMA and policy enforcement
 * @param keycloak - Initialized Keycloak instance
 */
constructor(keycloak: Keycloak);

Initialize Authorization (Deprecated)

Initializes the authorization client. This method is deprecated but still available.

/**
 * Initializes authorization client (deprecated)
 * @returns Promise that resolves when initialization completes
 * @deprecated This method is deprecated and may be removed in future versions
 */
init(): Promise<void>;

Usage Example:

import Keycloak from "keycloak-js";
import KeycloakAuthorization from "keycloak-js/keycloak-authz";

const keycloak = new Keycloak({
  url: "https://auth.mycompany.com",
  realm: "my-realm",
  clientId: "resource-server"
});

await keycloak.init();

const authorization = new KeycloakAuthorization(keycloak);

Authorization Request

Requests authorization based on permission ticket from UMA-protected resource server.

/**
 * Requests authorization using permission ticket or resource permissions
 * @param request - Authorization request with ticket or permissions
 * @returns Promise resolving to Requesting Party Token (RPT)
 */
authorize(request: AuthorizationRequest): Promise<string>;

Usage Examples:

// Handle 401 response with permission ticket
async function handleUnauthorizedResponse(response) {
  const wwwAuthHeader = response.headers.get("WWW-Authenticate");
  const ticket = extractTicketFromHeader(wwwAuthHeader);
  
  try {
    const rpt = await authorization.authorize({ ticket });
    
    // Retry original request with RPT
    const retryResponse = await fetch(originalUrl, {
      headers: {
        Authorization: `Bearer ${rpt}`
      }
    });
    
    return retryResponse;
  } catch (error) {
    console.error("Authorization denied:", error);
    showAccessDeniedMessage();
  }
}

// Request specific resource permissions
const rpt = await authorization.authorize({
  permissions: [
    {
      id: "photo-album",
      scopes: ["view", "edit"]
    },
    {
      name: "document-folder", 
      scopes: ["view"]
    }
  ]
});

// Request with multiple tickets
const rpt2 = await authorization.authorize({
  tickets: ["ticket1", "ticket2"],
  incrementalAuthorization: true
});

Entitlement Request

Obtains entitlements (RPT) for a specific resource server.

/**
 * Obtains entitlements (RPT) for specified resource server
 * @param resourceServerId - Client ID of the resource server
 * @param request - Optional authorization request with specific permissions
 * @returns Promise resolving to Requesting Party Token (RPT)
 */
entitlement(resourceServerId: string, request?: AuthorizationRequest): Promise<string>;

Usage Examples:

// Get all permissions user can access for resource server
const rpt = await authorization.entitlement("photo-service");

// Get specific permissions for resource server
const rpt = await authorization.entitlement("document-service", {
  permissions: [
    {
      name: "quarterly-reports",
      scopes: ["view", "download"]
    }
  ]
});

// Use RPT for subsequent API calls
const response = await fetch("/api/documents/quarterly-reports", {
  headers: {
    Authorization: `Bearer ${rpt}`
  }
});

RPT Access

Gets the current Requesting Party Token.

/**
 * Current Requesting Party Token (RPT) with permissions
 */
rpt?: string;

Usage Example:

// Check if we have a current RPT
if (authorization.rpt) {
  console.log("Current RPT:", authorization.rpt);
  
  // Use existing RPT for API calls
  const response = await fetch("/api/protected-resource", {
    headers: {
      Authorization: `Bearer ${authorization.rpt}`
    }
  });
} else {
  // Request new entitlement
  const rpt = await authorization.entitlement("my-resource-server");
}

Authorization Types

AuthorizationRequest

Configuration object for authorization requests.

interface AuthorizationRequest {
  /** Permission ticket from UMA-protected resource server */
  ticket?: string;
  
  /** Array of permission tickets */
  tickets?: string[];
  
  /** Array of specific resource permissions to request */
  permissions?: ResourcePermission[];
  
  /** Metadata controlling request processing */
  metadata?: AuthorizationMetadata;
  
  /** Whether to create permission requests for ticket resources */
  submit_request?: boolean;
  
  /** Enable incremental authorization */
  incrementalAuthorization?: boolean;
  
  /** Submitter identifier */
  submitterId?: string;
  
  /** Claim token for additional claims */
  claimToken?: string;
  
  /** Format of the claim token */
  claimTokenFormat?: string;
}

ResourcePermission

Represents a resource and its requested scopes.

interface ResourcePermission {
  /** Resource identifier or name */
  id?: string;
  
  /** Resource name */
  name?: string;
  
  /** Array of scope names to request for this resource */
  scopes?: string[];
}

AuthorizationMetadata

Controls how authorization requests are processed by the server.

interface AuthorizationMetadata {
  /** Include resource names in RPT permissions */
  response_include_resource_name?: boolean;
  
  /** Limit number of permissions in RPT */
  response_permissions_limit?: number;
}

UMA Authorization Flow

Complete example of handling UMA authorization flow:

class ResourceClient {
  constructor(resourceServerUrl, keycloak, authorization) {
    this.baseUrl = resourceServerUrl;
    this.keycloak = keycloak;
    this.authorization = authorization;
  }
  
  async accessResource(resourcePath) {
    try {
      // Try initial request
      let response = await this.makeRequest(resourcePath);
      
      if (response.status === 401) {
        // Handle UMA authorization
        response = await this.handleUMAAuth(response, resourcePath);
      }
      
      return response;
    } catch (error) {
      console.error("Resource access failed:", error);
      throw error;
    }
  }
  
  async makeRequest(resourcePath, rpt = null) {
    const token = rpt || this.keycloak.token;
    
    return fetch(`${this.baseUrl}${resourcePath}`, {
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json"
      }
    });
  }
  
  async handleUMAAuth(response, resourcePath) {
    const wwwAuth = response.headers.get("WWW-Authenticate");
    
    if (wwwAuth && wwwAuth.includes("UMA")) {
      const ticket = this.extractTicket(wwwAuth);
      
      try {
        // Request authorization with ticket
        const rpt = await this.authorization.authorize({
          ticket,
          submit_request: true
        });
        
        // Retry request with RPT
        return await this.makeRequest(resourcePath, rpt);
        
      } catch (authError) {
        console.error("Authorization denied:", authError);
        throw new Error("Access denied to resource");
      }
    }
    
    throw new Error("Unexpected authentication challenge");
  }
  
  extractTicket(wwwAuthHeader) {
    const ticketMatch = wwwAuthHeader.match(/ticket="([^"]+)"/);
    return ticketMatch ? ticketMatch[1] : null;
  }
}

// Usage
const resourceClient = new ResourceClient(
  "https://api.mycompany.com",
  keycloak,
  authorization
);

const data = await resourceClient.accessResource("/protected/documents/123");

Error Handling

Common authorization error scenarios:

// Authorization with error handling
async function requestPermissions() {
  try {
    const rpt = await authorization.authorize({
      permissions: [
        { name: "document-123", scopes: ["view", "edit"] }
      ]
    });
    
    console.log("Authorization successful");
    return rpt;
    
  } catch (error) {
    if (error.message.includes("access_denied")) {
      console.log("Access denied by policy");
      showPermissionDeniedDialog();
    } else if (error.message.includes("invalid_ticket")) {
      console.log("Invalid permission ticket");
      // Request new ticket from resource server
    } else {
      console.error("Authorization error:", error);
      showGenericErrorMessage();
    }
    
    throw error;
  }
}

// Entitlement with fallback
async function getEntitlements(resourceServerId) {
  try {
    return await authorization.entitlement(resourceServerId);
  } catch (error) {
    console.warn(`No entitlements for ${resourceServerId}:`, error);
    return null;
  }
}

Integration with Callback Pattern

The authorization functions support callback-style error handling:

// Using callback pattern (legacy style)
authorization.authorize({ ticket })
  .then((rpt) => {
    // onGrant callback - authorization successful
    console.log("Access granted, RPT:", rpt);
    retryRequestWithRPT(rpt);
  })
  .catch((error) => {
    if (error.type === "access_denied") {
      // onDeny callback - access denied
      console.log("Access denied by policy");
      showAccessDeniedUI();
    } else {
      // onError callback - unexpected error
      console.error("Authorization error:", error);
      showErrorUI();
    }
  });

docs

authentication.md

authorization.md

index.md

tile.json