Backstage Oidc Provider

Backstage est un formidable outil de tableau de bord pour les développeurs. Son système de plugin le rends particulièrement flexible. Cependant la configuration de ces plugins peut s’avérer fastidueuse. Backstage est en constante évolution, ce qui fait que la documentation à parfois du mal à suivre. C’est le cas depuis la migration vers le nouveau Backend system. Backstage dispose de plusieurs plugin permettant de se connecter à la majorité des providers d’identité via OIDC. Toutefois, si vous souhaitez connecter backstage à un provider personnalisé comme un Keycloak d’entreprise la documentation n’est pas vraiment clair. C’est ce que nous allons voir dans ce post.

Création du plugin

Backstage fournis un provider OIDC mais pas de plugin prêt à l’emploi. Nous allons devoir créer un module pour étendre le plugin Auth de backstage avec notre support de OIDC. Nous utilisons la CLI de backstage pour cela

1
2
3
backstage-cli new --select backend-module
# ? Enter the ID of the plugin [required] : auth
# ? Enter the ID of the module [required] : oidc

Backstage à maintenant générer une structure de plugin dans le dossier plugins/auth-backend-module-oidc. Commençons par ajouter la dépendences au provider OIDC fournis par Backstage

1
2
cd plugins/auth-backend-module-oidc
yarn add @backstage/plugin-auth-backend-module-oidc-provider

Une fois cela fait, dans le fichier src/module.ts, remplacer le contenu par le suivant:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { oidcAuthenticator } from '@backstage/plugin-auth-backend-module-oidc-provider';
import { authProvidersExtensionPoint, createOAuthProviderFactory } from '@backstage/plugin-auth-node'
import {
  coreServices,
  createBackendModule,
} from '@backstage/backend-plugin-api';
import { DEFAULT_NAMESPACE, stringifyEntityRef } from '@backstage/catalog-model';

export const authModuleOidc = createBackendModule({
  pluginId: 'auth',
  moduleId: 'oidc',
  register(reg) {
    reg.registerInit({
      deps: { logger: coreServices.logger, providers: authProvidersExtensionPoint },
      async init({ logger, providers }) {
        logger.info('Registering OIDC provider!');
        providers.registerProvider({
          providerId: 'sso-auth-provider',
          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, // The user's own identity
                  ent: [userRef], // A list of identities that the user claims ownership through
                }
              });
            }
          })
        });
      },
    });
  },
});

Notre plugin est maintenant opérationel. Vérifions que le module est bien activé dans packages/backend/index.ts. La ligne suivant doit être présente :

1
backend.add(import('backstage-plugin-auth-backend-module-oidc'))

Configuration de l’Api Factory

Nous allons maintenant référencer le plugin comme API d’autentification dans le front. Pour cela ajouter le code suivant dans le fichier packages/app/src/apis.ts :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import {
  AnyApiFactory,
  ApiRef,
  BackstageIdentityApi,
  configApiRef,
  createApiFactory,
  createApiRef,
  discoveryApiRef,
  oauthRequestApiRef,
  OpenIdConnectApi,
  ProfileInfoApi,
  SessionApi,
} from '@backstage/core-plugin-api';
import { OAuth2 } from '@backstage/core-app-api';

export const oidcAuthApiRef: ApiRef <
  OpenIdConnectApi & // The OIDC API that will handle authentification
  ProfileInfoApi & // Profile API for requesting user profile info from the auth provider in question
  BackstageIdentityApi & // Backstage Identity API to handle and associate the user profile with backstage identity
  SessionApi // Sesssion API, to handle the session the user will have while logged in
> = createApiRef({
  id: 'sso-auth-provider' // Can be anything as long as it doesn't conflict with other API ref IDs
})

export const apis: AnyApiFactory[] = [
  createApiFactory({
    api: oidcAuthApiRef,
    deps: {
      discoveryApi: discoveryApiRef,
      oauthRequestApi: oauthRequestApiRef,
      configApi: configApiRef
    },
    factory: ({ discoveryApi, oauthRequestApi, configApi }) => Oauth2.create({
      configApi,
      discoveryApi,
      oauthRequestApi,
      provider: {
        id: 'sso-auth-provider',
        title: 'Enterprise SSO',
        icon: () => null
      },
      environment: configApi.getOptionalString('auth.environment'),
      defaultScopes: ['openId', 'profile', 'email'],
      popupOptions: {
        size: {
          // fullscreen: true
          // or specify popup width and height
          width: 1000,
          height: 1000,
        }
      }
    })
  }),
  // Other createApiFactory
]

Ajout du bouton de connexion dans l’interface

Allons maintenant dans le fichier packages/app/src/App.tsx pour ajouter le bouton de connexion à backstage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { apis, oidcAuthApiRef } from './apis';
import { configApiRef, useApi } from '@backstage/core-plugin-api';

const app = createApp({
  // Other stuff...
  components: {
    SignInPage: props => (<SignInPage {...props} auto providers={ useApi(configApiRef).getString('auth.environment') === 'development' ? ['guest'] : [{
      id: 'sso-auth-provider',
      title: 'Enterprise SSO',
      message: 'Sign in with Enterprise SSO',
      apiRef: oidcAuthApiRef
    }]}/>)
  }
  // Other stuff...
})

Ajoutez la configuration suivante dans votre fichier app-config.local.yaml:

1
2
auth:
  environment: development

et dans le fichier app-config.production.yaml

1
2
3
4
5
6
7
8
9
auth:
  environment: production
  providers:
    guest: {}
    sso-auth-provider:
      production:
        metadataUrl: ${AUTH_REALM_URL}/.well-known/openid-configuration
        clientId: ${AUTH_MY_CLIENT_ID}
        clientSecret: ${AUTH_MY_CLIENT_SECRET}

Avec la configuration ci-dessus, l’authentification se fera comme guest en mode developement et via SSO en mode production. Pour plus d’information, vous pouvez consulter la documentation officielle

Félicitation votre application backstage supporte maintenant la connexion via OIDC. Jetez également un œil à l’intégration Keycloak pour l’import automatique des utilisateurs et des groupes dans Backstage.

0%