Help Needed: Keycloak Provider Integration Issue in Strapi v5 After Upgrade

Hello Strapi Community,

I’m trying to enable the Keycloak provider in Strapi 5. After some research, I found this article on integrating Keycloak with Strapi. The instructions work well for Strapi v4, but after upgrading to Strapi v5, I ran into issues and haven’t been able to resolve them.

Here’s my code. If anyone has experience with Strapi v5 and can help troubleshoot, I’d greatly appreciate it. Many thanks!

src/extensions/users-permissions/server/registry.ts

import { Core } from '@strapi/strapi';

interface GrantConfig {
  keycloak: {
    subdomain: string;
    clientId: string;
    clientSecret: string;
    callback: string;
    enabled: boolean;
  };
}

export const doRegisterKeycloakProvider = ({ strapi }: { strapi: Core.Strapi }) => {
  // Get the providers-registry service using the full path

  // const providersRegistry = strapi.container.get('plugin::users-permissions.providers');
  // const providersRegistry = strapi.plugin('users-permissions').service('providers-registry');

  const providersRegistry = strapi.service(
    'plugin::users-permissions.providers-registry',
  );

  // also i have try to pass
  // { name: 'keycloak', pluginId: 'keycloak-provider' },
  providersRegistry.register('keycloak', ({ purest }) => {
    return async ({ accessToken }) => {
      const pluginStore = strapi.store({
        type: 'plugin',
        name: 'users-permissions',
      });

      const config = (await pluginStore.get({ key: 'grant' })) as GrantConfig;
      const keycloakConfig = config?.keycloak;

      if (!keycloakConfig) {
        throw new Error('Keycloak configuration is missing');
      }

      const keycloak = purest({
        provider: 'keycloak',
        defaults: {
          baseUrl: `https://${keycloakConfig.subdomain}`,
        },
      });

      try {
        const { body } = await keycloak
          .get('protocol/openid-connect/userinfo')
          .auth(accessToken)
          .request();

        return {
          username: body.preferred_username,
          email: body.email,
          provider: 'keycloak',
        };
      } catch (error) {
        throw new Error('Failed to fetch user info from Keycloak');
      }
    };
  });
};

src/extensions/users-permissions/server/bootstrap.ts


import { Core } from '@strapi/strapi';

import { doRegisterKeycloakProvider } from './registry';

interface KeycloakConfig {
  enabled: boolean;
  icon: string;
  key: string;
  secret: string;
  subdomain: string;
  callback: string;
  scope: string[];
}

interface GrantConfig {
  keycloak: KeycloakConfig;
  [key: string]: any; // For other providers if needed
}

const getGrantConfig = (baseURL: string): GrantConfig => ({
  keycloak: {
    enabled: false,
    icon: 'keycloak',
    key: process.env.KEYCLOAK_CLIENT_ID || '',
    secret: process.env.KEYCLOAK_CLIENT_SECRET || '',
    subdomain: `${process.env.IDENTITY_ISSUER}`,
    callback: `${baseURL}/keycloak/callback`,
    scope: ['email'],
  },
});

export const bootstrapHandler = (
  bootstrap: (params: { strapi: Core.Strapi }) => Promise<void>,
) => {
  return async ({ strapi }: { strapi: Core.Strapi }) => {
    const pluginStore = strapi.store({
      type: 'plugin',
      name: 'users-permissions',
    });

    const storedGrantConfig =
      ((await pluginStore.get({ key: 'grant' })) as Partial<GrantConfig>) || {};

    await bootstrap({ strapi });

    const storedGrantConfigOnBootstrap =
      ((await pluginStore.get({ key: 'grant' })) as Partial<GrantConfig>) || {};

    const apiPrefix = strapi.config.get('api.rest.prefix');
    const baseURL = `${strapi.config.server.url}${apiPrefix}/auth`;
    const grantConfig = getGrantConfig(baseURL);

    // Merge configurations with type safety
    const newGrantConfig: GrantConfig = {
      ...grantConfig,
      ...storedGrantConfigOnBootstrap,
      ...storedGrantConfig,
      keycloak: {
        ...grantConfig.keycloak,
        ...(storedGrantConfigOnBootstrap.keycloak || {}),
        ...(storedGrantConfig.keycloak || {}),
      },
    };

    // Validate required fields
    if (newGrantConfig.keycloak.enabled) {
      if (!newGrantConfig.keycloak.key || !newGrantConfig.keycloak.secret) {
        console.warn('Keycloak is enabled but client ID or secret is missing');
      }
      if (!process.env.IDENTITY_PROVIDER) {
        console.warn('IDENTITY_PROVIDER environment variable is not set');
      }
    }

    await pluginStore.set({
      key: 'grant',
      value: newGrantConfig,
    });

    doRegisterKeycloakProvider({ strapi });
  };
};

src/extensions/users-permissions/strapi-server.ts

import { Core } from '@strapi/strapi';

import { bootstrapHandler } from './server/bootstrap';

interface StrapiPlugin {
  bootstrap: (params: { strapi: Core.Strapi }) => Promise<void>;
  [key: string]: any;
}

export default async (plugin: StrapiPlugin) => {
  return new Proxy(plugin, {
    get(target: StrapiPlugin, prop: string | symbol): any {
      if (prop === 'bootstrap') {
        return bootstrapHandler(target.bootstrap);
      }

      // Use type assertion for symbol access
      return Reflect.get(target, prop);
    },
  });
};

Error:
TypeError: providersRegistry.register is not a function at doRegisterKeycloakProvider