chore: remove old backend system guides

Signed-off-by: aramissennyeydd <aramis.sennyey@doordash.com>
This commit is contained in:
aramissennyeydd
2025-04-13 17:44:10 -04:00
parent d75e96cb34
commit 499fa53939
40 changed files with 21 additions and 4400 deletions
-75
View File
@@ -1,75 +0,0 @@
---
id: provider--old
title: Auth0 Authentication Provider
sidebar_label: Auth0
description: Adding Auth0 as an authentication provider in Backstage
---
:::info
This documentation is written for the old backend which has been replaced by
[the new backend system](../../backend-system/index.md), being the default since
Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new
backend system, you may want to read [its own article](./provider.md)
instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)!
:::
The Backstage `core-plugin-api` package comes with an Auth0 authentication
provider that can authenticate users using OAuth.
## Create an Auth0 Application
1. Log in to the [Auth0 dashboard](https://manage.auth0.com/dashboard/)
2. Navigate to **Applications**
3. Create an Application
- Name: Backstage (or your custom app name)
- Application type: Single Page Web Application
4. Click on the Settings tab
5. Add under `Application URIs` > `Allowed Callback URLs`:
`http://localhost:7007/api/auth/auth0/handler/frame`
6. Click `Save Changes`
## Configuration
The provider configuration can then be added to your `app-config.yaml` under the
root `auth` configuration:
```yaml
auth:
environment: development
providers:
auth0:
development:
clientId: ${AUTH_AUTH0_CLIENT_ID}
clientSecret: ${AUTH_AUTH0_CLIENT_SECRET}
domain: ${AUTH_AUTH0_DOMAIN_ID}
audience: ${AUTH_AUTH0_AUDIENCE}
connection: ${AUTH_AUTH0_CONNECTION}
connectionScope: ${AUTH_AUTH0_CONNECTION_SCOPE}
session:
secret: ${AUTH_SESSION_SECRET}
```
The Auth0 provider is a structure with these configuration keys:
- `clientId`: The Application client ID, found on the Auth0 Application page
- `clientSecret`: The Application client secret, found on the Auth0 Application
page
- `domain`: The Application domain, found on the Auth0 Application page
It additionally relies on the following configuration to function:
- `session.secret`: The session secret is a key used for signing and/or encrypting cookies set by the application to maintain session state. In this case, 'your session secret' should be replaced with a long, complex, and unique string that only your application knows.
Auth0 requires a session, so you need to give the session a secret key.
## Optional Configuration
- `audience`: The intended recipients of the token
- `connection`: Social identity provider name. To check the available social connections, please visit [Auth0 Social Connections](https://marketplace.auth0.com/features/social-connections).
- `connectionScope`: Additional scopes in the interactive token request. It should always be used in combination with the `connection` parameter
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `auth0AuthApi` reference and
`SignInPage` component as shown in
[Adding the provider to the sign-in page](../index.md#sign-in-configuration).
+1 -1
View File
@@ -8,7 +8,7 @@ description: Adding Auth0 as an authentication provider in Backstage
:::info
This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage
[version 1.24](../../releases/v1.24.0.md). If you are still on the old backend
system, you may want to read [its own article](./provider--old.md)
system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/auth0/provider--old.md)
instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
:::
@@ -1,60 +0,0 @@
---
id: provider--od
title: Bitbucket Server Authentication Provider
sidebar_label: Bitbucket Server
description: Adding Bitbucket Server OAuth as an authentication provider in Backstage
---
:::info
This documentation is written for the old backend which has been replaced by
[the new backend system](../../backend-system/index.md), being the default since
Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new
backend system, you may want to read [its own article](./provider.md)
instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)!
:::
The Backstage `core-plugin-api` package comes with a Bitbucket Server authentication provider that can authenticate
users using Bitbucket Server. This does **NOT** work with Bitbucket Cloud.
## Create an Application Link in Bitbucket Server
To add Bitbucket Server authentication, you must create an incoming application link. Follow the steps described in
the [Bitbucket Server documentation](https://confluence.atlassian.com/bitbucketserver/configure-an-incoming-link-1108483657.html)
to create one.
## Configuration
The provider configuration can then be added to your `app-config.yaml` under the root `auth` configuration:
```yaml
auth:
environment: development
providers:
bitbucketServer:
development:
host: bitbucket.org
clientId: ${AUTH_BITBUCKET_SERVER_CLIENT_ID}
clientSecret: ${AUTH_BITBUCKET_SERVER_CLIENT_SECRET}
```
The Bitbucket Server provider is a structure with two configuration keys:
- `clientId`: The client ID that was generated by Bitbucket, e.g. `b0f868455c15dcdff5c5fb5d173ae684`.
- `clientSecret`: The client secret tied to the generated client ID.
## Adding the provider to the Backstage frontend
To add the provider to the frontend, add the `bitbucketServerAuthApi` reference and `SignInPage` component as shown
in [Adding the provider to the sign-in page](../index.md#sign-in-configuration).
## Using Bitbucket Server for sign-in
In order to use the Bitbucket Server provider for sign-in, you must configure it with a `signIn.resolver`. See
the [Sign-In Resolver documentation](../identity-resolver.md) for more details on how this is done. Note that for the
Bitbucket Server provider, you'll want to use `bitbucketServer` as the provider ID,
and `providers.bitbucketServer.create` for the provider factory.
The `@backstage/plugin-auth-backend` plugin also comes with a built-in resolver that can be used if desired.
The `emailMatchingUserEntityProfileEmail` identifies users by matching their Bitbucket Server email address to the email
address of `User` entities in the catalog. Note that you must populate your catalog with matching entities or users will
not be able to sign in with this resolver.
+1 -1
View File
@@ -8,7 +8,7 @@ description: Adding Bitbucket Server OAuth as an authentication provider in Back
:::info
This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage
[version 1.24](../../releases/v1.24.0.md). If you are still on the old backend
system, you may want to read [its own article](./provider--old.md)
system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/bitbucketServer/provider--old.md)
instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)!
:::
-379
View File
@@ -1,379 +0,0 @@
---
id: identity-resolver--old
title: Sign-in Identities and Resolvers (Old Backend System)
description: An introduction to Backstage user identities and sign-in resolvers in the old backend system
---
:::info
This documentation is written for the old backend which has been replaced by
[the new backend system](../backend-system/index.md), being the default since
Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new
backend system, you may want to read [its own article](./identity-resolver.md)
instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)!
:::
By default, every Backstage auth provider is configured only for the use-case of
access delegation. This enables Backstage to request resources and actions from
external systems on behalf of the user, for example re-triggering a build in CI.
If you want to use an auth provider to sign in users, you need to explicitly configure
it have sign-in enabled and also tell it how the external identities should
be mapped to user identities within Backstage.
## Quick Start
> See [the auth docs](./index.md)
> for a full list of auth providers and their built-in sign-in resolvers.
Backstage projects created with `npx @backstage/create-app` come configured with a
sign-in resolver for GitHub guest access. This resolver makes all users share
a single "guest" identity and is only intended as a minimum requirement to quickly
get up and running. You can replace `github` for any of the other providers if you need.
This resolver should not be used in production, as it uses a single shared identity,
and has no restrictions on who is able to sign-in. Be sure to read through the rest
of this page to understand the Backstage identity system once you need to install
a resolver for your production environment.
The guest resolver can be useful for testing purposes too, and it looks like this:
```ts
signIn: {
resolver(_, ctx) {
const userRef = 'user:default/guest'
return ctx.issueToken({
claims: {
sub: userRef,
ent: [userRef],
},
}),
},
},
```
## Backstage User Identity
A user identity within Backstage is built up from two pieces of information, a
user [entity reference](../features/software-catalog/references.md), and a
set of ownership entity references.
When a user signs in, a Backstage token is generated with these two pieces of information,
which is then used to identify the user within the Backstage ecosystem.
The user entity reference should uniquely identify the logged in user in Backstage.
It is encouraged that a matching user entity also exists within the Software Catalog,
but it is not required. If the user entity exists in the catalog it can be used to
store additional data about the user. There may even be some plugins that require
this for them to be able to function.
The ownership references are also entity references, and it is likewise
encouraged that these entities exist within the catalog, but it is not a requirement.
The ownership references are used to determine what the user owns, as a set
of references that the user claims ownership though. For example, a user
Jane (`user:default/jane`) might have the ownership references `user:default/jane`,
`group:default/team-a`, and `group:default/admins`. Given these ownership claims,
any entity that is marked as owned by either of `user:jane`, `team-a`, or `admins` would
be considered owned by Jane.
The ownership claims often contain the user entity reference itself, but it is not
required. It is also worth noting that the ownership claims can also be used to
resolve other relations similar to ownership, such as a claim for a `maintainer` or
`operator` status.
The Backstage token that encapsulates the user identity is a JWT. The user entity
reference is stored in the `sub` claim of the payload, while the ownership references
are stored in a custom `ent` claim. Both the user and ownership references should
always be full entity references, as opposed to shorthands like just `jane` or `user:jane`.
## Sign-in Resolvers
Signing in a user into Backstage requires a mapping of the user identity from the
third-party auth provider to a Backstage user identity. This mapping can vary quite
a lot between different organizations and auth providers, and because of that there's
no default way to resolve user identities. The auth provider that one wants to use
for sign-in must instead be configured with a sign-in resolver, which is a function
that is responsible for creating this user identity mapping.
The input to the sign-in resolver function is the result of a successful log in with
the given auth provider, as well as a context object that contains various helpers
for looking up users and issuing tokens. There are also a number of built-in sign-in
resolvers that can be used, which are covered a bit further down.
Note that while it possible to configure multiple auth providers to be used for sign-in,
you should take care when doing so. It is best to make sure that the different auth
providers either do not have any user overlap, or that any users that are able to log
in with multiple providers always end up with the same Backstage identity.
### Custom Resolver Example
Let's look at an example of a custom sign-in resolver for the Google auth provider.
This all typically happens within your `packages/backend/src/plugins/auth.ts` file,
which is responsible for setting up and configuring the auth backend plugin.
You provide the resolver as part of the options you pass when creating a new auth
provider factory. This means you need to replace the default Google provider with
one that you create. Be sure to also include the existing `defaultAuthProviderFactories`
if you want to keep all of the built-in auth providers installed.
Now let's look at the example, with the rest of the commentary being made with in
the code comments:
```ts
// File: packages/backend/src/plugins/auth.ts
import {
createRouter,
providers,
defaultAuthProviderFactories,
} from '@backstage/plugin-auth-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
...env,
providerFactories: {
...defaultAuthProviderFactories,
google: providers.google.create({
signIn: {
resolver: async (info, ctx) => {
const {
profile: { email },
} = info;
// Profiles are not always guaranteed to have an email address.
// You can also find more provider-specific information in `info.result`.
// It typically contains a `fullProfile` object as well as ID and/or access
// tokens that you can use for additional lookups.
if (!email) {
throw new Error('User profile contained no email');
}
// You can add your own custom validation logic here.
// Logins can be prevented by throwing an error like the one above.
myEmailValidator(email);
// This example resolver simply uses the local part of the email as the name.
const [name] = email.split('@');
// This helper function handles sign-in by looking up a user in the catalog.
// The lookup can be done either by reference, annotations, or custom filters.
//
// The helper also issues a token for the user, using the standard group
// membership logic to determine the ownership references of the user.
return ctx.signInWithCatalogUser({
entityRef: { name },
});
},
},
}),
},
});
}
```
### Built-in Resolvers
You don't always have to write your own custom resolver. The auth backend plugin provides
built-in resolvers for many of the common sign-in patterns. You access these via the `resolvers`
property of each of the auth provider integrations. For example, the Google provider has
a built in resolver that works just like the one we defined above:
```ts
// File: packages/backend/src/plugins/auth.ts
export default async function createPlugin(
// ...
return await createRouter({
// ...
providerFactories: {
// ...
google: providers.google.create({
signIn: {
resolver: providers.google.resolvers.emailLocalPartMatchingUserEntityName(),
},
});
}
})
)
```
There are also other options, like the this one that looks up a user
by matching the `google.com/email` annotation of user entities in the catalog:
```ts
providers.google.create({
signIn: {
resolver: providers.google.resolvers.emailMatchingUserEntityAnnotation(),
},
});
```
## Custom Ownership Resolution
If you want to have more control over the membership resolution and token generation
that happens during sign-in you can replace `ctx.signInWithCatalogUser` with a set
of lower-level calls:
```ts
// File: packages/backend/src/plugins/auth.ts
import { getDefaultOwnershipEntityRefs } from '@backstage/plugin-auth-backend';
export default async function createPlugin(
// ...
return await createRouter({
// ...
providerFactories: {
// ...
google: async ({ profile: { email } }, ctx) => {
if (!email) {
throw new Error('User profile contained no email');
}
// This step calls the catalog to look up a user entity. You could for example
// replace it with a call to a different external system.
const { entity } = await ctx.findCatalogUser({
annotations: {
'acme.org/email': email,
},
});
// In this step we extract the ownership references from the user entity using
// the standard logic. It uses a reference to the entity itself, as well as the
// target of each `memberOf` relation where the target is of the kind `Group`.
//
// If you replace the catalog lookup with something that does not return
// an entity you will need to replace this step as well.
//
// You might also replace it if you for example want to filter out certain groups.
//
// Note that `getDefaultOwnershipEntityRefs` only includes groups to which the
// user has a direct MEMBER_OF relationship. It's perfectly fine to include
// groups that the user is transitively part of in the claims array, but the
// catalog doesn't currently provide a direct way of accessing this list of
// groups.
const ownershipRefs = getDefaultOwnershipEntityRefs(entity);
// The last step is to issue the token, where we might provide more options in the future.
return ctx.issueToken({
claims: {
sub: stringifyEntityRef(entity),
ent: ownershipRefs,
},
});
};
}
})
)
```
## Sign-In without Users in the Catalog
While populating the catalog with organizational data unlocks more powerful ways
to browse your software ecosystem, it might not always be a viable or prioritized
option. However, even if you do not have user entities populated in your catalog, you
can still sign in users. As there are currently no built-in sign-in resolvers for
this scenario you will need to implement your own.
Signing in a user that doesn't exist in the catalog is as simple as skipping the
catalog lookup step from the above example. Rather than looking up the user, we
instead immediately issue a token using whatever information is available. One caveat
is that it can be tricky to determine the ownership references, although it can
be achieved for example through a lookup to an external service. You typically
want to at least use the user itself as a lone ownership reference.
Because we no longer use the catalog as an allow-list of users, it is often important
that you limit what users are allowed to sign in. This could be a simple email domain
check like in the example below, or you might for example look up the GitHub organizations
that the user belongs to using the user access token in the provided result object.
```ts
// File: packages/backend/src/plugins/auth.ts
import { createRouter, providers } from '@backstage/plugin-auth-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
import {
stringifyEntityRef,
DEFAULT_NAMESPACE,
} from '@backstage/catalog-model';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
...env,
providerFactories: {
google: providers.google.create({
signIn: {
resolver: async ({ profile }, ctx) => {
if (!profile.email) {
throw new Error(
'Login failed, user profile does not contain an email',
);
}
// Split the email into the local part and the domain.
const [localPart, domain] = profile.email.split('@');
// Next we verify the email domain. It is recommended to include this
// kind of check if you don't look up the user in an external service.
if (domain !== 'acme.org') {
throw new Error(
`Login failed, this email ${profile.email} does not belong to the expected domain`,
);
}
// By using `stringifyEntityRef` we ensure that the reference is formatted correctly
const userEntity = stringifyEntityRef({
kind: 'User',
name: localPart,
namespace: DEFAULT_NAMESPACE,
});
return ctx.issueToken({
claims: {
sub: userEntity,
ent: [userEntity],
},
});
},
},
}),
},
});
}
```
## AuthHandler
Similar to a custom sign-in resolver, you can also write a custom auth handler
function which is used to verify and convert the auth response into the profile
that will be presented to the user. This is where you can customize things like
display name and profile picture.
This is also the place where you can do authorization and validation of the user
and throw errors if the user should not be allowed access in Backstage.
```ts
// File: packages/backend/src/plugins/auth.ts
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
...
providerFactories: {
google: providers.google.create({
authHandler: async ({
fullProfile // Type: passport.Profile,
idToken // Type: (Optional) string,
}) => {
// Custom validation code goes here
return {
profile: {
email,
picture,
displayName,
}
};
}
})
}
})
}
```
+1 -1
View File
@@ -7,7 +7,7 @@ description: An introduction to Backstage user identities and sign-in resolvers
:::info
This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage
[version 1.24](../releases/v1.24.0.md). If you are still on the old backend
system, you may want to read [its own article](./identity-resolver--old.md)
system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/identity-resolver--old.md)
instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)!
:::
-289
View File
@@ -1,289 +0,0 @@
---
id: oidc--old
title: OIDC provider from scratch
description: This section shows how to use an OIDC provider from scratch, same steps apply for custom providers.
---
:::info
This documentation is written for the old backend which has been replaced by
[the new backend system](../backend-system/index.md), being the default since
Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new
backend system, you may want to read [its own article](./oidc.md)
instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)!
:::
This section shows how to use an OIDC provider from scratch, same steps apply for custom
providers. Please note these steps are for using a provider, not how to implement one,
and Backstage recommends creating custom providers specific to the IDP, so we'll use a
`azureOIDC` provider throughout this example, feel free to change any of those refs
to your provider name.
## Summary
To add providers not enabled by default like OIDC, we need to follow some steps, we
assume you already have a sign in page to which we'll add the provider so users can
sign in through the provider. In simple steps here's how you enable the provider:
- Create an API reference to identify the provider.
- Create the API factory that will handle the authentication.
- Add or reuse an auth provider so you can authenticate.
- Add or reuse a resolver to handle the result from the authentication.
- Configure the provider to access your 3rd party auth solution.
- Add the provider to sign in page so users can login with it.
We'll explain each step more in detail next.
### The API reference
An API reference exist for the sake of **Dependency Injection**, check [Utility APIs][4]
for extended explanation.
In this OIDC example, we'll create the API reference directly in the
`packages/app/src/apis.ts` file, it is not a requirement to put the reference in this
file. Any location will do as long as it's available to be imported to where the API
factory is, as well as easily accessible to the rest of the application so any package
and plugin can inject the API instance when necessary.
An example of such would be when you use an auth provider from a library installed with
NPM, or any other library repository, you would import the API ref from the library.
```ts
export const azureOIDCAuthApiRef: ApiRef<
OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
> = createApiRef({
id: 'auth.my-custom-provider',
});
```
Please note a few things, the ID can be anything you want as long as it doesn't conflict
with other refs, backstage recommends to use a custom name that references your custom
provider, for example we are using OIDC protocol with Azure, so we could use something
like `auth.azure.oidc` as well.
Also we're exporting this reference, as well as the `typings`, we need to
be able to import this reference anywhere in the app, and the `typings` will tell typescript
what instance we're getting from DI when injecting the API. In this case we are defining
an API for authentication, so we tell TS that this instance complies with 4 API
interfaces:
- The OICD API that will handle authentication.
- Profile API for requesting user profile info from the auth provider in question.
- Backstage identity API to handle and associate the user profile with backstage identity.
- Session API, to handle the session the user will have while logged in.
### The API Factory
A factory is a function that can take some parameters or dependencies and return an
instance of something, in our case it will be a function that requests some backstage
APIs and use them to create an instance of an OIDC API provider.
Please note that this function only runs (creates the instance) when somewhere else in
the app you request the DI to give you an instance of the OIDC provider using the API ref
defined above, and the DI will only run this function the first time, from then on any
other DI injection will just receive the same instance created the first time, basically
the instance is cached by the DI library, a singleton.
Let's add our OIDC API factory to the APIs array in the `packages/app/src/apis.ts` file:
```ts title="packages/app/src/apis.ts"
/* highlight-add-next-line */
import { OAuth2 } from '@backstage/core-app-api';
export const apis: AnyApiFactory[] = [
/* highlight-add-start */
createApiFactory({
api: azureOIDCAuthApiRef,
deps: {
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
OAuth2.create({
configApi,
discoveryApi,
oauthRequestApi,
provider: {
id: 'my-auth-provider',
title: 'My custom auth provider',
icon: () => null,
},
environment: configApi.getOptionalString('auth.environment'),
defaultScopes: ['openid', 'profile', 'email'],
popupOptions: {
// optional, used to customize login in popup size
size: {
fullscreen: true,
},
/**
* or specify popup width and height
* size: {
width: 1000,
height: 1000,
}
*/
},
}),
}),
/* highlight-add-end */
// ..
];
```
Please note we're importing the `OAuth2` class from `@backstage/core-app-api` effectively
delegating the authentication to it. Also we're using the `my-auth-provider` ID to tell
`OAuth2` to use the auth provider we'll define in the next section, and added the default
scopes to request ID, profile, email and user read permissions.
## The Auth Provider
The Auth Provider is responsible for authenticating with the 3rd party service, and give
us back the credentials, here's where you pick which protocol to use, be it Auth0, OAuth2,
OIDC, SAML or any other that your 3rd party IDP provider supports.
For this example we'll use OIDC, we pass a factory to the `providerFactories` object with
the ID you picked to represent the Auth provider, this ID has to match with the provider's
`id` inside the API factory, the yaml config provider key under `auth.providers`, and the
callback URI provider segment (you'll have to configure your IDP to handle the callback
URI properly).
```ts
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
logger: env.logger,
config: env.config,
database: env.database,
discovery: env.discovery,
tokenManager: env.tokenManager,
providerFactories: {
...defaultAuthProviderFactories,
/* highlight-add-next-line */
'my-auth-provider': providers.oidc.create({}),
},
// ..
})
```
### The Resolver
Resolvers exist to map user identity from the 3rd party (in this case an azure IDP
provider) to the backstage user identity, for a detailed explanation check the
[Identity Resolver][1] page, it explains how to write a custom resolver as well as
linking the built in resolvers of backstage.
The default OIDC provider does not support SignIn, we need to add such support by
adding a resolver for a SignIn request.
The OIDC provider doesn't provide any build-in resolvers, so we'll need to define our own:
```ts
import {
DEFAULT_NAMESPACE,
/* highlight-add-next-line */
stringifyEntityRef,
} from '@backstage/catalog-model';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
logger: env.logger,
config: env.config,
database: env.database,
discovery: env.discovery,
tokenManager: env.tokenManager,
providerFactories: {
...defaultAuthProviderFactories,
'my-auth-provider': providers.oidc.create({
/* highlight-add-start */
signIn: {
resolver(info, ctx) {
const userRef = stringifyEntityRef({
kind: 'User',
name: info.result.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
},
});
},
},
/* highlight-add-end */
}),
},
// ..
})
```
### The configuration
Since we are using our custom OIDC Auth Provider, we need to add a configuration based
on the provider used, in this case based on OIDC protocol (remember the 3rd party has to
support the protocol).
In this example we'll configure OIDC with `my-auth-provider`, to do so we need to
[Create app registration][2] in the Azure console, the only difference is that the
`http://localhost:7007/api/auth/microsoft/handler/frame` URL needs to change to
`http://localhost:7007/api/auth/my-auth-provider/handler/frame`.
Then we need to configure the env variables for the provider, based on the provider's code
in `plugins/auth-backend/src/providers/oidc/provider.ts` we need the following variables
in the `app-config.yaml`:
```yaml title="app-config.yaml"
auth:
environment: development
### Providing an auth.session.secret will enable session support in the auth-backend
session:
secret: ${SESSION_SECRET}
providers:
my-auth-provider:
development:
metadataUrl: https://example.com/.well-known/openid-configuration
clientId: ${AUTH_MY_CLIENT_ID}
clientSecret: ${AUTH_MY_CLIENT_SECRET}
```
Anything enclosed in `${}` can be replaced directly in the yaml, or provided as
environment variables, the way you obtain all these except `scope` and `prompt` is to
check the App Registration you created:
- `clientId`: Grab from the Overview page.
- `clientSecret`: Can only be seen when creating the secret, if you lose it you'll need a
new secret.
- `metadataUrl`: In Overview > Endpoints tab, grab OpenID Connect metadata document URL.
- `authorizationUrl` and `tokenUrl`: Open the `metadataUrl` in a browser, that json will
hold these 2 urls somewhere in there.
- `tokenEndpointAuthMethod`: Don't define it, use the default unless you know what it does.
- `tokenSignedResponseAlg`: Don't define it, use the default unless you know what it does.
- `scope`: Only used if we didn't specify `defaultScopes` in the provider's factory,
basically the same thing.
- `prompt`: Recommended to use `auto` so the browser will request login to the IDP if the
user has no active session.
Note that for the time being, any change in this yaml file requires a restart of the app,
also you need to have the `session.secret` part to use OIDC (some other providers might
need this as well) to support user sessions.
### The Sign In provider
The last step is to add the provider to the `SignInPage` so users can sign in with your
new provider, please follow the [Sign In Configuration][3] docs, here's where you import
and use the API reference we defined earlier.
## Note
These steps apply to most if not all the providers, including custom providers, the main
difference between different providers will be the contents of the API factory, the code
in the Auth Provider Factory, the resolver, and the different variables each provider
needs in the YAML config or env variables.
[1]: https://backstage.io/docs/auth/identity-resolver
[2]: https://backstage.io/docs/auth/microsoft/provider#create-an-app-registration-on-azure
[3]: https://backstage.io/docs/auth/#sign-in-configuration
[4]: https://backstage.io/docs/api/utility-apis
+1 -1
View File
@@ -7,7 +7,7 @@ description: This section shows how to use an OIDC provider from scratch, same s
:::info
This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage
[version 1.24](../releases/v1.24.0.md). If you are still on the old backend
system, you may want to read [its own article](./oidc--old.md)
system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/oidc--old.md)
instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)!
:::
-177
View File
@@ -1,177 +0,0 @@
---
id: service-to-service-auth--old
title: Service to Service Auth
# prettier-ignore
description: This section describes how to use service to service authentication, both internally within Backstage plugins and towards external services.
---
:::info
This documentation is written for the old backend which has been replaced by
[the new backend system](../backend-system/index.md), being the default since
Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new
backend system, you may want to read [its own article](./identity-resolver.md)
instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)!
:::
This article describes the steps needed to introduce _service-to-service auth_ (formerly _backend-to-backend_ auth).
This allows plugin backends to determine whether a given request originates from
a legitimate Backstage plugin (or other external caller), by requiring a special
type of service-to-service token which is signed with a shared secret.
When enabling this protection on your Backstage backend plugins, for example the
catalog, other callers in the ecosystem such as the search indexer and
scaffolder would need to present a valid token to the catalog to be able to
request its contents.
## Setup
In a newly created Backstage app, the backend is setup up to not require any
auth at all. This means that generated service-to-service tokens are empty, and
that incoming requests are not validated. If you want to enable
service-to-service auth, the first step is to switch out the following line in
your backend setup at `packages/backend/src/index.ts`:
```ts title="packages/backend/src/index.ts"
/* highlight-remove-next-line */
const tokenManager = ServerTokenManager.noop();
/* highlight-add-next-line */
const tokenManager = ServerTokenManager.fromConfig(config, { logger: root });
```
By switching from the no-op `ServiceTokenManager` to one created from config,
you enable service-to-service auth for any plugin that implements it. The local
development setup will generally not be impacted by this, as temporary keys are
generated under the hood. But for the production setup, this means you must now
provide a shared secret that enables your backend plugins to communicate with
each other.
Backstage service-to-service tokens are currently always signed with a single
secret key. It needs to be shared across all backend plugins and services that
ones wishes to communicate across. The key can be any base64 encoded secret.
The following command can be used to generate such a key in a terminal:
```bash
node -p 'require("crypto").randomBytes(24).toString("base64")'
```
Then place it in the backend configuration, either as a direct value or
injected as an env variable.
```yaml
# commonly in your app-config.production.yaml
backend:
auth:
keys:
- secret: <the string returned by the above crypto command>
# - secret: ${BACKEND_SECRET} - if you want to use an env variable instead
```
**NOTE**: For ease of development, we auto-generate a key for you if you haven't
configured a secret in dev mode. You _must set your own secret_ in order for
service-to-service auth to work in production; the `ServiceTokenManager` will
throw an exception in production if it has no keys to work with, which will lead
to the backend failing to start up.
## Usage in Backend Plugins
There are a few steps if you want to make use of the service-to-service auth in
your own backend plugin. First you need to add the `TokenManager` dependency to
the `createRouter` options. Typically as `tokenManager: TokenManager`. Along
with this you'll need to ask users to start providing this new dependency in
their backend setup code.
Once the `TokenManager` is available, you use the `.getToken()` method to generate
a new token for any outgoing requests towards other Backstage backend plugins.
This method should be called for every request that you make; do not store the
token for later use. The `TokenManager` implementations should already cache
tokens as needed. The returned token should then be added as a `Bearer` token
for the upstream request, for example:
```ts
const { token } = await this.tokenManager.getToken();
const response = await fetch(pluginBackendApiUrl, {
method: 'GET',
headers: {
...headers,
Authorization: `Bearer ${token}`,
},
});
```
To authenticate an incoming request you use the `.authenticate(token)` method.
At the time of writing this method doesn't return anything, it will simply
throw if the token is invalid.
```ts
await tokenManager.authenticate(token); // throws if token is invalid
```
## Usage in External Callers
If you have enabled server-to-server auth, you may be interested in generating
tokens in code that is external to Backstage itself. External callers may even
be written in other languages than Node.js. This section explains how to generate
a valid token yourself.
The token must be a JWT with a `HS256` signature, using the raw base64 decoded
value of the configured key as the secret. It must also have the following payload:
- `sub`: "backstage-server" (only this value supported currently)
- `exp`: one hour from the time it was generated, in epoch seconds
> NOTE: The JWT must encode the `alg` header as a protected header, such as with
> [setProtectedHeader](https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md#setprotectedheader).
## Granular Access Control
We plan to build out the service-to-service auth to be much more powerful in the
future, but before that is done there are a few tricks you can use with the
current system to harden your deployments. This section assumes that you have
already split your backend plugins into more than one backend deployment, in
order to scale or isolate them.
The backend auth configuration has support for providing multiple keys, for
example:
```yaml
backend:
auth:
keys:
- secret: my-secret-key-1
- secret: my-secret-key-2
- secret: my-secret-key-3
```
The first key will be used for signing requests, while all of the keys will be
used for validation. This means that you can set up an asymmetric configuration
where some backend deployments do not have access to each other.
For example, consider the case where we have split up the catalog, scaffolder,
and search plugin into three separate backend deployments. We can use the
following configurations to allow both the scaffolder and search plugin to speak
to the
catalog, but not the other way around, and to not allow any communication between
the scaffolder and search plugins.
```yaml
# catalog config
backend:
auth:
keys:
- secret: my-secret-key-catalog
- secret: my-secret-key-scaffolder
- secret: my-secret-key-search
# scaffolder config
backend:
auth:
keys:
- secret: my-secret-key-scaffolder
# search config
backend:
auth:
keys:
- secret: my-secret-key-search
```
+2 -2
View File
@@ -8,7 +8,7 @@ description: This section describes service to service authentication works, bot
:::info
This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage
[version 1.24](../releases/v1.24.0.md). If you are still on the old backend
system, you may want to read [its own article](./service-to-service-auth--old.md)
system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/service-to-service-auth--old.md)
instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)!
:::
@@ -219,7 +219,7 @@ provider's documentation.
The subject returned from the token verification will become part of the
credentials object that the request recipient plugins get. All subjects will have the prefix
`external:`, but you can also provide a custom subjectPrefix which will get appended before the
`external:`, but you can also provide a custom `subjectPrefix` which will get appended before the
subject returned from your JWKS service (ex. `external:custom-prefix:sub`).
Callers must pass along tokens with requests in the `Authorization` header when