chore: remove old backend system guides
Signed-off-by: aramissennyeydd <aramis.sennyey@doordash.com>
This commit is contained in:
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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)!
|
||||
:::
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
};
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -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)!
|
||||
:::
|
||||
|
||||
|
||||
@@ -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
@@ -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)!
|
||||
:::
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user