docs: split up backend-to-backend and api request authentication docs
Signed-off-by: MT Lewis <mtlewis@users.noreply.github.com>
This commit is contained in:
+8
-75
@@ -1,45 +1,12 @@
|
||||
---
|
||||
id: authenticate-api-requests
|
||||
title: Authenticating API Requests
|
||||
description: Guide for authenticating API requests within Backstage
|
||||
---
|
||||
|
||||
> This document has been moved over from the community contributed docs
|
||||
> directory and will continue to evolve as request authentication/authorization
|
||||
> becomes a first-class citizen of Backstage. The approach described here is now
|
||||
> fully supported and functional end-to-end, but will eventually be replaced in
|
||||
> favor of a built-in framework that will broadly support authentication and
|
||||
> authorization needs within Backstage.
|
||||
|
||||
# Authenticate API requests
|
||||
|
||||
The Backstage backend APIs are by default available without authentication. To
|
||||
avoid evil-doers from accessing or modifying data, one might use a network
|
||||
protection mechanism such as a firewall or an authenticating reverse proxy. For
|
||||
Backstage instances that are available on the Internet one can instead use the
|
||||
experimental IdentityClient as outlined below.
|
||||
The Backstage backend APIs are by default available without authentication. To avoid evil-doers from accessing or modifying data, one might use a network protection mechanism such as a firewall or an authenticating reverse proxy. For Backstage instances that are available on the Internet one can instead use the experimental IdentityClient as outlined below.
|
||||
|
||||
API requests from frontend plugins include an authorization header with a
|
||||
Backstage identity token acquired when the user logs in. By adding a middleware
|
||||
that verifies said token to be valid and signed by Backstage, non-authenticated
|
||||
requests can be blocked with a 401 Unauthorized response.
|
||||
API requests from frontend plugins include an authorization header with a Backstage identity token acquired when the user logs in. By adding a middleware that verifies said token to be valid and signed by Backstage, non-authenticated requests can be blocked with a 401 Unauthorized response.
|
||||
|
||||
**NOTE**: Enabling this means that Backstage will stop working for guests, as no
|
||||
token is issued for them.
|
||||
**NOTE**: Enabling this means that Backstage will stop working for guests, as no token is issued for them.
|
||||
|
||||
Since the middleware always expects a valid token, API requests from backend
|
||||
plugins will need one as well. Backends have no concept of a Backstage identity
|
||||
so instead they use a token generated using a shared key from the
|
||||
`app-config.yaml`. You can generate a unique key for your app in a terminal, and
|
||||
set the `BACKEND_SECRET` environment variable to the resulting value.
|
||||
|
||||
```bash
|
||||
node -p 'require("crypto").randomBytes(24).toString("base64")'
|
||||
```
|
||||
|
||||
As techdocs HTML pages load assets without an Authorization header the code
|
||||
below also sets a token cookie when the user logs in (and when the token is
|
||||
about to expire).
|
||||
As techdocs HTML pages load assets without an Authorization header the code below also sets a token cookie when the user logs in (and when the token is about to expire).
|
||||
|
||||
```typescript
|
||||
// packages/backend/src/index.ts from a create-app deployment
|
||||
@@ -93,13 +60,7 @@ async function main() {
|
||||
const token =
|
||||
IdentityClient.getBearerToken(req.headers.authorization) ||
|
||||
req.cookies['token'];
|
||||
|
||||
// Authenticate all requests originating from backends by default
|
||||
const isValidServerToken = authEnv.tokenManager.validateToken(token);
|
||||
if (!isValidServerToken) {
|
||||
req.user = await identity.authenticate(token);
|
||||
}
|
||||
|
||||
req.user = await identity.authenticate(token);
|
||||
if (!req.headers.authorization) {
|
||||
// Authorization header may be forwarded by plugin requests
|
||||
req.headers.authorization = `Bearer ${token}`;
|
||||
@@ -221,10 +182,9 @@ const app = createApp({
|
||||
// ...
|
||||
```
|
||||
|
||||
**NOTE**: Most Backstage frontend plugins come with the support for the
|
||||
`IdentityApi`. In case you already have a dozen of internal ones, you may need
|
||||
to update those too. Assuming you follow the common plugin structure, the
|
||||
changes to your front-end may look like:
|
||||
**NOTE**: Most Backstage frontend plugins come with the support for the `IdentityApi`.
|
||||
In case you already have a dozen of internal ones, you may need to update those too.
|
||||
Assuming you follow the common plugin structure, the changes to your front-end may look like:
|
||||
|
||||
```diff
|
||||
// plugins/internal-plugin/src/api.ts
|
||||
@@ -301,30 +261,3 @@ export const plugin = createPlugin({
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
In the (probably unlikely) case that you need to authenticate from a backend
|
||||
plugin, the plugin environment contains a `tokenManager` that will provide a
|
||||
server token to use in the request. It is a noop `tokenManager` by default --
|
||||
you'll need to instantiate a new one using the secret from your config instead:
|
||||
|
||||
```diff
|
||||
// packages/backend/src/index.ts
|
||||
|
||||
function makeCreateEnv(config: Config) {
|
||||
const root = getRootLogger();
|
||||
const reader = UrlReaders.default({ logger: root, config });
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
|
||||
root.info(`Created UrlReader ${reader}`);
|
||||
|
||||
const cacheManager = CacheManager.fromConfig(config);
|
||||
const databaseManager = DatabaseManager.fromConfig(config);
|
||||
- const tokenManager = ServerTokenManager.noop();
|
||||
+ const tokenManager = ServerTokenManager.fromConfig(config);
|
||||
```
|
||||
|
||||
With this `tokenManager`, you can then generate a server token for requests:
|
||||
|
||||
```
|
||||
const { token } = await this.tokenManager.getToken();
|
||||
```
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
id: backend-to-backend-auth
|
||||
title: Backend-to-Backend Authentication
|
||||
description:
|
||||
Guide for authenticating API requests between Backstage plugin backends
|
||||
---
|
||||
|
||||
This tutorial describes the steps needed to handle _backend-to-backend
|
||||
authentication_, which allows plugin backends to determine whether a given
|
||||
request originates from a legitimate Backstage backend by verifying a token
|
||||
signed with a shared secret. This system has limited use for now, but will be
|
||||
needed to support the upcoming framework for permissions and authorization (see
|
||||
[the PRFC on the topic](https://github.com/backstage/backstage/pull/7761) for
|
||||
more details).
|
||||
|
||||
Backends have no concept of a Backstage identity, so instead they use a token
|
||||
generated using a shared key stored in config. You can generate a unique key for
|
||||
your app in a terminal, and set the `BACKEND_SECRET` environment variable to the
|
||||
resulting value.
|
||||
|
||||
```bash
|
||||
node -p 'require("crypto").randomBytes(24).toString("base64")'
|
||||
```
|
||||
|
||||
Requests originating from a backend plugin can be authenticated by decorating
|
||||
them with a backend token. Backend tokens can be generated using a
|
||||
`TokenManager`, which can be passed to plugin backends via the
|
||||
`PluginEnvironment`. The `TokenManager` provided in new Backstage instances
|
||||
generated by `create-app` is a stub, which returns empty tokens and accepts any
|
||||
input string as valid. To enable backend-to-backend authentication, you'll need
|
||||
to instantiate a new one using the secret from your config instead:
|
||||
|
||||
```diff
|
||||
// packages/backend/src/index.ts
|
||||
|
||||
function makeCreateEnv(config: Config) {
|
||||
const root = getRootLogger();
|
||||
const reader = UrlReaders.default({ logger: root, config });
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
|
||||
root.info(`Created UrlReader ${reader}`);
|
||||
|
||||
const cacheManager = CacheManager.fromConfig(config);
|
||||
const databaseManager = DatabaseManager.fromConfig(config);
|
||||
- const tokenManager = ServerTokenManager.noop();
|
||||
+ const tokenManager = ServerTokenManager.fromConfig(config);
|
||||
```
|
||||
|
||||
With this `tokenManager`, you can then generate a server token for requests:
|
||||
|
||||
```typescript
|
||||
const { token } = await this.tokenManager.getToken();
|
||||
|
||||
const response = await fetch(pluginBackendApiUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
...headers,
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
You can use the same `tokenManager` to validate tokens supplied on incoming
|
||||
requests:
|
||||
|
||||
```typescript
|
||||
const isValidServerToken = await tokenManager.validateToken(token);
|
||||
if (!isValidServerToken) {
|
||||
throw new UnauthorizedError();
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user