Skip to main content
Version: Next

OIDC provider from scratch

info

This documentation is written for the new frontend system, which is the default in new Backstage apps. If your Backstage app still uses the old frontend system, read the old frontend system version of this guide instead.

This section shows how to enable and use the Backstage OIDC provider.

Summary

OIDC is a protocol which has numerous implementations. It's likely that many of your users won't know what the OIDC protocol is, but they will recognise your OIDC implementation. Backstage supplies a generic oidc authorization strategy. You should re-badge this with the name and branding of your OIDC implementation, so that your users will recognise it on the Backstage sign-in page.

For example, if your organization uses Keycloak, you would re-badge the OIDC provider as Keycloak and tell users to Sign In using Keycloak.

Steps

The Backstage OIDC provider is not enabled by default. You need to:

  1. Install and configure the OIDC backend module.
  2. Configure the provider in app-config.yaml.
  3. Configure a sign-in resolver.
  4. Add the provider to the Backstage sign-in page.

For simplicity, we assume that you only have a single OIDC provider in your Backstage installation.

We'll explain each step in detail next.

Backend Installation

To add the OIDC provider to the backend, install the provider module:

from your Backstage root directory
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-oidc-provider

Then add it to your backend in packages/backend/src/index.ts:

packages/backend/src/index.ts
backend.add(import('@backstage/plugin-auth-backend'));
backend.add(import('@backstage/plugin-auth-backend-module-oidc-provider'));

Configuration

Register an OIDC client application for Backstage with your OIDC provider (for example, Keycloak). Then add the provider configuration to your app-config.yaml:

app-config.yaml
auth:
environment: development
session:
secret: ${AUTH_SESSION_SECRET}
providers:
oidc:
development:
metadataUrl: https://example.com/.well-known/openid-configuration
clientId: ${AUTH_OIDC_CLIENT_ID}
clientSecret: ${AUTH_OIDC_CLIENT_SECRET}
signIn:
resolvers:
- resolver: emailMatchingUserEntityProfileEmail

Anything enclosed in ${} can be replaced directly in the YAML, or provided as environment variables.

Required parameters

These parameters must always be set.

  • clientId: The client ID from your OIDC provider.
  • clientSecret: The client secret tied to the client ID.
  • metadataUrl: The OpenID Connect metadata document URL, for example https://example.com/.well-known/openid-configuration.

The OIDC provider also requires auth.session.secret to be set.

Optional parameters

These parameters have implicit default values. Don't override them unless you know what you're doing.

  • callbackUrl: Override the default callback URL used by the OIDC provider.
  • timeout: Override the default timeout for calls to the OIDC provider.
  • tokenEndpointAuthMethod
  • tokenSignedResponseAlg
  • additionalScopes: Requests additional scopes on top of the default openid profile email scopes. Do not configure scope directly, as the OIDC provider will reject configurations that include it.
  • prompt: Recommended to use auto so the browser will request sign-in to the identity provider if the user has no active session.
  • sessionDuration: Lifespan of the user session.
  • startUrlSearchParams: A dictionary of search (query) parameters for the OIDC authorization start URL. Don't define it unless you want to change the identity provider's behavior. (For example, you could set the organization parameter to guide users towards a particular sign-in option that your organization prefers.) Note: the start URL is controlled by the browser, so this feature is only for improving the Backstage user experience.
Config Reloading

Backstage does not yet support hot reloading of auth provider configuration. Any changes to this YAML file require a restart of Backstage.

Resolvers

Resolvers map the user identity from the OIDC provider to the Backstage user identity.

The default OIDC provider has a choice of built-in resolvers that you configure in app-config.yaml under auth.providers.oidc.<environment>.signIn.resolvers:

app-config.yaml
auth:
providers:
oidc:
development:
# ...
signIn:
resolvers:
- resolver: emailMatchingUserEntityProfileEmail

If none of the built-in resolvers are suitable, you can write a custom resolver. See Building Custom Resolvers for details.

The following example shows a custom resolver that maps users by their OIDC sub claim:

packages/backend/src/index.ts
import { createBackendModule } from '@backstage/backend-plugin-api';
import {
authProvidersExtensionPoint,
createOAuthProviderFactory,
} from '@backstage/plugin-auth-node';
import { oidcAuthenticator } from '@backstage/plugin-auth-backend-module-oidc-provider';
import {
stringifyEntityRef,
DEFAULT_NAMESPACE,
} from '@backstage/catalog-model';

const myAuthProviderModule = createBackendModule({
pluginId: 'auth',
moduleId: 'custom-oidc-provider',
register(reg) {
reg.registerInit({
deps: { providers: authProvidersExtensionPoint },
async init({ providers }) {
providers.registerProvider({
providerId: 'oidc',
factory: createOAuthProviderFactory({
authenticator: oidcAuthenticator,
async signInResolver(info, ctx) {
const userRef = stringifyEntityRef({
kind: 'User',
name: info.result.fullProfile.userinfo.sub,
namespace: DEFAULT_NAMESPACE,
});
return ctx.issueToken({
claims: {
sub: userRef,
ent: [userRef],
},
});
},
}),
});
},
});
},
});

// ...
backend.add(import('@backstage/plugin-auth-backend'));
// Use the custom module instead of the default OIDC module
backend.add(myAuthProviderModule);

For a more detailed explanation about resolvers, see the Identity Resolver page.

Adding the provider to the sign-in page

The last step is to configure the sign-in page in the frontend to use your OIDC provider.

Since OIDC does not have a built-in auth API ref in @backstage/core-plugin-api, you need to create a custom one and wire it up using a SignInPageBlueprint. The following example shows how to set this up for a Keycloak-branded OIDC provider in packages/app/src/App.tsx:

packages/app/src/App.tsx
import { createApp } from '@backstage/frontend-defaults';
import {
OpenIdConnectApi,
ProfileInfoApi,
BackstageIdentityApi,
SessionApi,
} from '@backstage/core-plugin-api';
import { OAuth2 } from '@backstage/core-app-api';
import { SignInPageBlueprint } from '@backstage/plugin-app-react';
import { SignInPage } from '@backstage/core-components';
import {
createApiRef,
createFrontendModule,
configApiRef,
discoveryApiRef,
oauthRequestApiRef,
ApiBlueprint,
} from '@backstage/frontend-plugin-api';

const keycloakAuthApiRef = createApiRef<
OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
>().with({
id: 'auth.keycloak',
});

const keycloakAuthApi = ApiBlueprint.make({
name: 'keycloak',
params: defineParams =>
defineParams({
api: keycloakAuthApiRef,
deps: {
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
OAuth2.create({
configApi,
discoveryApi,
oauthRequestApi,
environment: configApi.getOptionalString('auth.environment'),
provider: {
id: 'oidc',
title: 'Keycloak',
icon: () => null,
},
defaultScopes: ['openid', 'profile', 'email'],
}),
}),
});

const signInPage = SignInPageBlueprint.make({
params: {
loader: async () => props =>
(
<SignInPage
{...props}
provider={{
id: 'keycloak-auth-provider',
title: 'Keycloak',
message: 'Sign In using Keycloak',
apiRef: keycloakAuthApiRef,
}}
/>
),
},
});

export default createApp({
features: [
// ...
createFrontendModule({
pluginId: 'app',
extensions: [keycloakAuthApi, signInPage],
}),
],
});

The id of the API ref (e.g. 'auth.keycloak') and the id used in the SignInPage provider configuration (e.g. 'keycloak-auth-provider') can be customized. However, the provider.id passed to OAuth2.create must remain 'oidc' to match Backstage's generic OIDC auth strategy on the backend.

Note

You can configure sign-in to use a redirect flow with no pop-up by adding enableExperimentalRedirectFlow: true to the root of your app-config.yaml.

For more information about sign-in configuration, see Sign-in Configuration.