Authentication and Authorization with Keycloak
Load users and groups from Keycloak, enabling use of multiple authentication providers to be applied to Backstage entities.
Keycloak backend plugin for Backstage
The Keycloak backend plugin integrates Keycloak into Backstage.
Capabilities
The Keycloak backend plugin has the following capabilities:
- Synchronization of Keycloak users in a realm
- Synchronization of Keycloak groups and their users in a realm
For administrators
Installation
Install the Backstage package into the backend. When not integrating with a published package, clone the repository locally and add the Backstage as follows:
yarn workspace backend add @janus-idp/backstage-plugin-keycloak-backend
Configuration
Legacy Backend Configuration
-
Add the following configuration to the
app-config.yaml
file:app-config.yamlcatalog:
providers:
keycloakOrg:
default:
# Remove the `/auth` if using keycloak 17+
baseUrl: https://<keycloak_host>/auth
loginRealm: ${KEYCLOAK_REALM}
realm: ${KEYCLOAK_REALM}
clientId: ${KEYCLOAK_CLIENTID}
clientSecret: ${KEYCLOAK_CLIENTSECRET} -
Register the plugin in the
packages/backend/src/plugins/catalog.ts
file. You can also configure a schedule in this step. However, there are possible ways of configuration, such as:-
Configure a schedule inside the
app-config.yaml
file:app-config.yamlcatalog:
providers:
keycloakOrg:
default:
# ...
schedule: # optional; same options as in TaskScheduleDefinition
# supports cron, ISO duration, "human duration" as used in code
frequency: { minutes: 30 }
# supports ISO duration, "human duration" as used in code
timeout: { minutes: 3 }
initialDelay: { seconds: 15 }Then use the configured scheduler by adding the following to the
packages/backend/src/plugins/catalog.ts
:packages/backend/src/plugins/catalog.tsimport { KeycloakOrgEntityProvider } from '@janus-idp/backstage-plugin-keycloak-backend';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* ... other processors and/or providers ... */
builder.addEntityProvider(
KeycloakOrgEntityProvider.fromConfig(env.config, {
id: 'development',
logger: env.logger,
scheduler: env.scheduler,
}),
);
const { processingEngine, router } = await builder.build();
await processingEngine.start();
return router;
}
NOTE
If you have made any changes to the schedule in the
app-config.yaml
file, then restart to apply the changes.
-
Add a schedule directly inside the
packages/backend/src/plugins/catalog.ts
file as follows:packages/backend/src/plugins/catalog.tsimport { KeycloakOrgEntityProvider } from '@janus-idp/backstage-plugin-keycloak-backend';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* ... other processors and/or providers ... */
builder.addEntityProvider(
KeycloakOrgEntityProvider.fromConfig(env.config, {
id: 'development',
logger: env.logger,
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 3 },
initialDelay: { seconds: 15 },
}),
}),
);
const { processingEngine, router } = await builder.build();
await processingEngine.start();
return router;
}
NOTE
If both the
schedule
(hard-coded schedule) andscheduler
(app-config.yaml
schedule) option are provided in thepackages/backend/src/plugins/catalog.ts
, thescheduler
option takes precedence. However, if the schedule inside theapp-config.yaml
file is not configured, then theschedule
option is used.
-
-
Optional: override the default Keycloak query parameters. Configure the parameters inside the
app-config.yaml
file:app-config.yamlcatalog:
providers:
keycloakOrg:
default:
# ...
userQuerySize: 500 # Optional
groupQuerySize: 250 # Optional -
Optional: provide a transformer function for user/group to mutate the entity before their ingestion into catalog. Extend
packages/backend/src/plugins/catalog.ts
with customuserTransformer
andgroupTransformer
functions:packages/backend/src/plugins/catalog.tsimport {
GroupTransformer,
UserTransformer,
} from '@janus-idp/backstage-plugin-keycloak-backend';
// Suffix user entity name with realm name
const userTransformer: UserTransformer = async (
entity,
user,
realm,
groups,
) => {
entity.metadata.name = `${entity.metadata.name}_${realm}`;
return entity;
};
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
/* ... other processors and/or providers ... */
builder.addEntityProvider(
KeycloakOrgEntityProvider.fromConfig(env.config, {
id: 'development',
logger: env.logger,
userTransformer,
}),
);
const { processingEngine, router } = await builder.build();
await processingEngine.start();
return router;
}userTransformer
is an async function that is expected to resolve toUserEntity
object orundefined
(if you want to reject the entity) and accepts following parameters:entity
: The output of the default parseruser
: Keycloak user representationrealm
: Realm namegroups
: Data about available groups
groupTransformer
is an async function that is expected to resolve toGroupEntity
object orundefined
(if you want to reject the entity) and accepts following parameters:entity
: The output of the default parsergroup
: Keycloak group representationrealm
: Realm name
New Backend Configuration
-
Add the following configuration to the
app-config.yaml
file, and customize the schedule to fit your needs:app-config.yamlcatalog:
providers:
keycloakOrg:
default:
# Remove the `/auth` if using keycloak 17+
baseUrl: https://<keycloak_host>/auth
loginRealm: ${KEYCLOAK_REALM}
realm: ${KEYCLOAK_REALM}
clientId: ${KEYCLOAK_CLIENTID}
clientSecret: ${KEYCLOAK_CLIENTSECRET}
schedule: # Mandatory; same options as in TaskScheduleDefinition
# supports cron, ISO duration, "human duration" as used in code
frequency: { minutes: 30 } # Customize this to fit your needs
# supports ISO duration, "human duration" as used in code
timeout: { minutes: 3 } # Customize this to fit your needs
initialDelay: { seconds: 15 } # Customize this to fit your needs -
Register the plugin in the
packages/backend/src/index.ts
file:packages/backend/src/index.tsconst backend = createBackend();
backend.add(import('@janus-idp/backstage-plugin-keycloak-backend/alpha'));
backend.start(); -
Optional: To configure custom transformer function for user/group to mutate the entity generated by the keycloak-backend. Create a new backend module with the
yarn new
command and add your custom user and group transformers to thekeycloakTransformerExtensionPoint
. Then install this new backend module into your backstage backend. Below is an example of how the backend module can be defined:plugins/<module-name>/src/module.tsimport {
GroupTransformer,
keycloakTransformerExtensionPoint,
UserTransformer,
} from '@janus-idp/backstage-plugin-keycloak-backend';
const customGroupTransformer: GroupTransformer = async (
entity,
realm,
groups,
) => {
/* apply transformations */
return entity;
};
const customUserTransformer: UserTransformer = async (
entity,
user,
realm,
groups,
) => {
/* 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);
},
});
},
});
IMPORTANT
The
pluginId
for the module MUST be set tocatalog
to match thepluginId
of thekeycloak-backend
or else the module will fail to initialize.
Communication between Backstage and Keycloak is enabled by using the Keycloak API. Username/password or client credentials are supported authentication methods.
The following table describes the parameters that you can configure to enable the plugin under catalog.providers.keycloakOrg.<ENVIRONMENT_NAME>
object in the app-config.yaml
file:
Name | Description | Default Value | Required |
---|---|---|---|
baseUrl | Location of the Keycloak server, such as https://localhost:8443/auth . Note that Keycloak 17+ omits the /auth context path. | "" | Yes |
realm | Realm to synchronize | master | No |
loginRealm | Realm used to authenticate | master | No |
username | Username to authenticate | "" | Yes if using password based authentication |
password | Password to authenticate | "" | Yes if using password based authentication |
clientId | Client ID to authenticate | "" | Yes if using client credentials based authentication |
clientSecret | Client Secret to authenticate | "" | Yes if using client credentials based authentication |
userQuerySize | Number of users to query at a time | 100 | No |
groupQuerySize | Number of groups to query at a time | 100 | No |
When using client credentials, the access type must be set to confidential
and service accounts must be enabled. You must also add the following roles from the realm-management
client role:
query-groups
query-users
view-users
Limitations
If you have self-signed or corporate certificate issues, you can set the following environment variable before starting Backstage:
NODE_TLS_REJECT_UNAUTHORIZED=0
NOTE
The solution of setting the NODE_TLS_REJECT_UNAUTHORIZED
environment variable is not recommended.
For users
Imported users and groups in Backstage using Keycloak plugin
After configuring the plugin successfully, the plugin imports the users and groups each time when started.
After the first import is complete, you can select User to list the users from the catalog page:
You can see the list of users on the page:
When you select a user, you can see the information imported from Keycloak:
You can also select a group, view the list, and select or view the information imported from Keycloak for a group: