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:
MT Lewis
2021-11-25 12:20:28 +00:00
parent 40ea710c14
commit 422de86abe
2 changed files with 79 additions and 75 deletions
@@ -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();
```
+71
View File
@@ -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();
}
```