Chapter 2. Authenticating with Red Hat Single Sign-On (RHSSO) To authenticate users with Red Hat Single Sign-On (RHSSO): Enable the OpenID Connect (OIDC) authentication provider in RHDH . Provision users from Red Hat Single-Sign On (RHSSO) to the software catalog . 2.1. Enabling authentication with Red Hat Single-Sign On (RHSSO) To authenticate users with Red Hat Single Sign-On (RHSSO), enable the OpenID Connect (OIDC) authentication provider in Red Hat Developer Hub. Prerequisites You added a custom Developer Hub application configuration , and have sufficient permissions to modify it. You have sufficient permissions in RHSSO to create and manage a realm. Procedure To allow Developer Hub to authenticate with RHSSO, complete the steps in RHSSO, to create a realm and a user and register the Developer Hub application : Use an existing realm, or create a realm , with a distinctive Name such as <my_realm> . Save the value for the step: RHSSO realm base URL , such as: <your_rhsso_URL> /auth/realms/ <your_realm> . To register your Developer Hub in RHSSO, in the created realm, create a Client ID , with: Client ID : A distinctive client ID, such as <RHDH> . Valid redirect URIs : Set to the OIDC handler URL: https:// <RHDH_URL> /api/auth/oidc/handler/frame . Navigate to the Credentials tab and copy the Client secret . Save the values for the step: Client ID Client Secret To prepare for the verification steps, in the same realm, get the credential information for an existing user or create a user . Save the user credential information for the verification steps. To add your RHSSO credentials to your Developer Hub secrets, edit your Developer Hub secrets, such as secrets-rhdh , and add the following key/value pairs: AUTH_OIDC_CLIENT_ID Enter the saved Client ID . AUTH_OIDC_CLIENT_SECRET Enter the saved Client Secret . AUTH_OIDC_METADATA_URL Enter the saved RHSSO realm base URL . To set up the RHSSO authentication provider in your Developer Hub custom configuration, edit your custom Developer Hub ConfigMap such as app-config-rhdh , and add the following lines to the app-config-rhdh.yaml content: app-config-rhdh.yaml fragment with mandatory fields to enable authentication with RHSSO auth: environment: production providers: oidc: production: metadataUrl: USD{AUTH_OIDC_METADATA_URL} clientId: USD{AUTH_OIDC_CLIENT_ID} clientSecret: USD{AUTH_OIDC_CLIENT_SECRET} signInPage: oidc environment: production Mark the environment as production to hide the Guest login in the Developer Hub home page. metadataUrl , clientId , clientSecret To configure the OIDC provider with your secrets. sigInPage: oidc To enable the OIDC provider as default sign-in provider. Optional: Consider adding the following optional fields: dangerouslyAllowSignInWithoutUserInCatalog: true To enable authentication without requiring to provision users in the Developer Hub software catalog. Warning Use this option to explore Developer Hub features, but do not use it in production. app-config-rhdh.yaml fragment with optional field to allow authenticating users absent from the software catalog auth: environment: production providers: oidc: production: metadataUrl: USD{AUTH_OIDC_METADATA_URL} clientId: USD{AUTH_OIDC_CLIENT_ID} clientSecret: USD{AUTH_OIDC_CLIENT_SECRET} signInPage: oidc dangerouslyAllowSignInWithoutUserInCatalog: true callbackUrl RHSSO callback URL. app-config-rhdh.yaml fragment with optional callbackURL field auth: providers: oidc: production: callbackUrl: USD{AUTH_OIDC_CALLBACK_URL} tokenEndpointAuthMethod Token endpoint authentication method. app-config-rhdh.yaml fragment with optional tokenEndpointAuthMethod field auth: providers: oidc: production: tokenEndpointAuthMethod: USD{AUTH_OIDC_TOKEN_ENDPOINT_METHOD} tokenSignedResponseAlg Token signed response algorithm. app-config-rhdh.yaml fragment with optional tokenSignedResponseAlg field auth: providers: oidc: production: tokenSignedResponseAlg: USD{AUTH_OIDC_SIGNED_RESPONSE_ALG} scope RHSSO scope. app-config-rhdh.yaml fragment with optional scope field auth: providers: oidc: production: scope: USD{AUTH_OIDC_SCOPE} signIn.resolvers Declarative resolvers to override the default resolver: emailLocalPartMatchingUserEntityName . The authentication provider tries each sign-in resolver until it succeeds, and fails if none succeed. app-config-rhdh.yaml fragment with optional callbackURL field auth: providers: oidc: production: signIn: resolvers: - resolver: preferredUsernameMatchingUserEntityName - resolver: emailMatchingUserEntityProfileEmail - resolver: emailLocalPartMatchingUserEntityName auth.backstageTokenExpiration To modify the Developer Hub token expiration from its default value of one hour, note that this refers to the validity of short-term cryptographic tokens, not the session duration. The expiration value must be set between 10 minutes and 24 hours. app-config-rhdh.yaml fragment with optional auth.backstageTokenExpiration field auth: backstageTokenExpiration: { minutes: <user_defined_value> } Security consideration If multiple valid refresh tokens are issued due to frequent refresh token requests, older tokens will remain valid until they expire. To enhance security and prevent potential misuse of older tokens, enable a refresh token rotation strategy in your RHBK realm. From the Configure section of the navigation menu, click Realm Settings . From the Realm Settings page, click the Tokens tab. From the Refresh tokens section of the Tokens tab, toggle the Revoke Refresh Token to the Enabled position. Verification Go to the Developer Hub login page. Your Developer Hub sign-in page displays Sign in using OIDC and the Guest user sign-in is disabled. Log in with OIDC by using the saved Username and Password values. 2.2. Provisioning users from Red Hat Single-Sign On (RHSSO) to the software catalog Prerequisites You enabled authentication with RHSSO . Procedure To enable RHSSO member discovery, edit your custom Developer Hub ConfigMap, such as app-config-rhdh , and add the following lines to the app-config-rhdh.yaml content: app-config.yaml fragment with mandatory keycloakOrg fields dangerouslyAllowSignInWithoutUserInCatalog: false catalog: providers: keycloakOrg: default: baseUrl: USD{AUTH_OIDC_METADATA_URL} clientId: USD{AUTH_OIDC_CLIENT_ID} clientSecret: USD{AUTH_OIDC_CLIENT_SECRET} dangerouslyAllowSignInWithoutUserInCatalog: false Allow authentication only for users present in the Developer Hub software catalog. baseUrl Your RHSSO server URL, defined when enabling authentication with RHSSO . clientId Your Developer Hub application client ID in RHSSO, defined when enabling authentication with RHSSO . clientSecret Your Developer Hub application client secret in RHSSO, defined when enabling authentication with RHSSO . Optional: Consider adding the following optional fields: realm Realm to synchronize. Default value: master . app-config.yaml fragment with optional realm field catalog: providers: keycloakOrg: default: realm: master loginRealm Realm used to authenticate. Default value: master . app-config.yaml fragment with optional loginRealm field catalog: providers: keycloakOrg: default: loginRealm: master userQuerySize User number to query simultaneously. Default value: 100 . app-config.yaml fragment with optional userQuerySize field catalog: providers: keycloakOrg: default: userQuerySize: 100 groupQuerySize Group number to query simultaneously. Default value: 100 . app-config.yaml fragment with optional groupQuerySize field catalog: providers: keycloakOrg: default: groupQuerySize: 100 schedule.frequency To specify custom schedule frequency. Supports cron, ISO duration, and "human duration" as used in code. app-config.yaml fragment with optional schedule.frequency field catalog: providers: keycloakOrg: default: schedule: frequency: { hours: 1 } schedule.timeout To specify custom timeout. Supports ISO duration and "human duration" as used in code. app-config.yaml fragment with optional schedule.timeout field catalog: providers: keycloakOrg: default: schedule: timeout: { minutes: 50 } schedule.initialDelay To specify custom initial delay. Supports ISO duration and "human duration" as used in code. app-config.yaml fragment with optional schedule.initialDelay field catalog: providers: keycloakOrg: default: schedule: initialDelay: { seconds: 15} Verification Check the console logs to verify that the synchronization is completed. Successful synchronization example: {"class":"KeycloakOrgEntityProvider","level":"info","message":"Read 3 Keycloak users and 2 Keycloak groups in 1.5 seconds. Committing...","plugin":"catalog","service":"backstage","taskId":"KeycloakOrgEntityProvider:default:refresh","taskInstanceId":"bf0467ff-8ac4-4702-911c-380270e44dea","timestamp":"2024-09-25 13:58:04"} {"class":"KeycloakOrgEntityProvider","level":"info","message":"Committed 3 Keycloak users and 2 Keycloak groups in 0.0 seconds.","plugin":"catalog","service":"backstage","taskId":"KeycloakOrgEntityProvider:default:refresh","taskInstanceId":"bf0467ff-8ac4-4702-911c-380270e44dea","timestamp":"2024-09-25 13:58:04"} Log in with an RHSSO account. 2.3. Creating a custom transformer to provision users from Red Hat Single-Sign On (RHSSO) to the software catalog To customize how RHSSO users and groups are mapped to Red Hat Developer Hub entities, you can create a backend module that uses the keycloakTransformerExtensionPoint to provide custom user and group transformers for the Keycloak backend. Prerequisites You have enabled provisioning users from Red Hat Single-Sign On (RHSSO) to the software catalog . Procedure Create a new backend module with the yarn new command. Add your custom user and group transformers to the keycloakTransformerExtensionPoint . The following is an example of how the backend module can be defined: plugins/ <module-name> /src/module.ts import { GroupTransformer, keycloakTransformerExtensionPoint, UserTransformer, } from '@janus-idp/backstage-plugin-keycloak-backend'; const customGroupTransformer: GroupTransformer = async ( entity, // entity output from default parser realm, // Keycloak realm name groups, // Keycloak group representation ) => { /* apply transformations */ return entity; }; const customUserTransformer: UserTransformer = async ( entity, // entity output from default parser user, // Keycloak user representation realm, // Keycloak realm name groups, // Keycloak group representation ) => { /* apply transformations */ return entity; }; export const keycloakBackendModuleTransformer = createBackendModule({ pluginId: 'catalog', moduleId: 'keycloak-transformer', register(reg) { reg.registerInit({ deps: { keycloak: keycloakTransformerExtensionPoint, }, async init({ keycloak }) { keycloak.setUserTransformer(customUserTransformer); keycloak.setGroupTransformer(customGroupTransformer); /* highlight-add-end */ }, }); }, }); Important The module's pluginId must be set to catalog to match the pluginId of the keycloak-backend ; otherwise, the module fails to initialize. Install this new backend module into your Developer Hub backend. backend.add(import(backstage-plugin-catalog-backend-module-keycloak-transformer)) Verification Developer Hub imports the users and groups each time when started. Check the console logs to verify that the synchronization is completed. Successful synchronization example: {"class":"KeycloakOrgEntityProvider","level":"info","message":"Read 3 Keycloak users and 2 Keycloak groups in 1.5 seconds. Committing...","plugin":"catalog","service":"backstage","taskId":"KeycloakOrgEntityProvider:default:refresh","taskInstanceId":"bf0467ff-8ac4-4702-911c-380270e44dea","timestamp":"2024-09-25 13:58:04"} {"class":"KeycloakOrgEntityProvider","level":"info","message":"Committed 3 Keycloak users and 2 Keycloak groups in 0.0 seconds.","plugin":"catalog","service":"backstage","taskId":"KeycloakOrgEntityProvider:default:refresh","taskInstanceId":"bf0467ff-8ac4-4702-911c-380270e44dea","timestamp":"2024-09-25 13:58:04"} After the first import is complete, navigate to the Catalog page and select User to view the list of users. When you select a user, you see the information imported from RHSSO. You can select a group, view the list, and access or review the information imported from RHSSO. You can log in with an RHSSO account.

Commands:
auth: environment: production providers: oidc: production: metadataUrl: USD{AUTH_OIDC_METADATA_URL} clientId: USD{AUTH_OIDC_CLIENT_ID} clientSecret: USD{AUTH_OIDC_CLIENT_SECRET} signInPage: oidc
auth: environment: production providers: oidc: production: metadataUrl: USD{AUTH_OIDC_METADATA_URL} clientId: USD{AUTH_OIDC_CLIENT_ID} clientSecret: USD{AUTH_OIDC_CLIENT_SECRET} signInPage: oidc dangerouslyAllowSignInWithoutUserInCatalog: true
auth: providers: oidc: production: callbackUrl: USD{AUTH_OIDC_CALLBACK_URL}
auth: providers: oidc: production: tokenEndpointAuthMethod: USD{AUTH_OIDC_TOKEN_ENDPOINT_METHOD}
auth: providers: oidc: production: tokenSignedResponseAlg: USD{AUTH_OIDC_SIGNED_RESPONSE_ALG}
auth: providers: oidc: production: scope: USD{AUTH_OIDC_SCOPE}
auth: providers: oidc: production: signIn: resolvers: - resolver: preferredUsernameMatchingUserEntityName - resolver: emailMatchingUserEntityProfileEmail - resolver: emailLocalPartMatchingUserEntityName
auth: backstageTokenExpiration: { minutes: <user_defined_value> }
dangerouslyAllowSignInWithoutUserInCatalog: false catalog: providers: keycloakOrg: default: baseUrl: USD{AUTH_OIDC_METADATA_URL} clientId: USD{AUTH_OIDC_CLIENT_ID} clientSecret: USD{AUTH_OIDC_CLIENT_SECRET}
catalog: providers: keycloakOrg: default: realm: master
catalog: providers: keycloakOrg: default: loginRealm: master
catalog: providers: keycloakOrg: default: userQuerySize: 100
catalog: providers: keycloakOrg: default: groupQuerySize: 100
catalog: providers: keycloakOrg: default: schedule: frequency: { hours: 1 }
catalog: providers: keycloakOrg: default: schedule: timeout: { minutes: 50 }
catalog: providers: keycloakOrg: default: schedule: initialDelay: { seconds: 15}
{"class":"KeycloakOrgEntityProvider","level":"info","message":"Read 3 Keycloak users and 2 Keycloak groups in 1.5 seconds. Committing...","plugin":"catalog","service":"backstage","taskId":"KeycloakOrgEntityProvider:default:refresh","taskInstanceId":"bf0467ff-8ac4-4702-911c-380270e44dea","timestamp":"2024-09-25 13:58:04"} {"class":"KeycloakOrgEntityProvider","level":"info","message":"Committed 3 Keycloak users and 2 Keycloak groups in 0.0 seconds.","plugin":"catalog","service":"backstage","taskId":"KeycloakOrgEntityProvider:default:refresh","taskInstanceId":"bf0467ff-8ac4-4702-911c-380270e44dea","timestamp":"2024-09-25 13:58:04"}
import { GroupTransformer, keycloakTransformerExtensionPoint, UserTransformer, } from '@janus-idp/backstage-plugin-keycloak-backend'; const customGroupTransformer: GroupTransformer = async ( entity, // entity output from default parser realm, // Keycloak realm name groups, // Keycloak group representation ) => { /* apply transformations */ return entity; }; const customUserTransformer: UserTransformer = async ( entity, // entity output from default parser user, // Keycloak user representation realm, // Keycloak realm name groups, // Keycloak group representation ) => { /* apply transformations */ return entity; }; export const keycloakBackendModuleTransformer = createBackendModule({ pluginId: 'catalog', moduleId: 'keycloak-transformer', register(reg) { reg.registerInit({ deps: { keycloak: keycloakTransformerExtensionPoint, }, async init({ keycloak }) { keycloak.setUserTransformer(customUserTransformer); keycloak.setGroupTransformer(customGroupTransformer); /* highlight-add-end */ }, }); }, });
backend.add(import(backstage-plugin-catalog-backend-module-keycloak-transformer))
{"class":"KeycloakOrgEntityProvider","level":"info","message":"Read 3 Keycloak users and 2 Keycloak groups in 1.5 seconds. Committing...","plugin":"catalog","service":"backstage","taskId":"KeycloakOrgEntityProvider:default:refresh","taskInstanceId":"bf0467ff-8ac4-4702-911c-380270e44dea","timestamp":"2024-09-25 13:58:04"} {"class":"KeycloakOrgEntityProvider","level":"info","message":"Committed 3 Keycloak users and 2 Keycloak groups in 0.0 seconds.","plugin":"catalog","service":"backstage","taskId":"KeycloakOrgEntityProvider:default:refresh","taskInstanceId":"bf0467ff-8ac4-4702-911c-380270e44dea","timestamp":"2024-09-25 13:58:04"}