From 422de86abe612d81367b0d8c0246d9cd8446ecb7 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Thu, 25 Nov 2021 12:20:28 +0000 Subject: [PATCH] docs: split up backend-to-backend and api request authentication docs Signed-off-by: MT Lewis --- .../tutorials/authenticate-api-requests.md | 83 ++----------------- docs/tutorials/backend-to-backend-auth.md | 71 ++++++++++++++++ 2 files changed, 79 insertions(+), 75 deletions(-) rename {docs => contrib/docs}/tutorials/authenticate-api-requests.md (69%) create mode 100644 docs/tutorials/backend-to-backend-auth.md diff --git a/docs/tutorials/authenticate-api-requests.md b/contrib/docs/tutorials/authenticate-api-requests.md similarity index 69% rename from docs/tutorials/authenticate-api-requests.md rename to contrib/docs/tutorials/authenticate-api-requests.md index 33d7d2cb31..9f651ddd1a 100644 --- a/docs/tutorials/authenticate-api-requests.md +++ b/contrib/docs/tutorials/authenticate-api-requests.md @@ -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(); -``` diff --git a/docs/tutorials/backend-to-backend-auth.md b/docs/tutorials/backend-to-backend-auth.md new file mode 100644 index 0000000000..b49a9fe3b5 --- /dev/null +++ b/docs/tutorials/backend-to-backend-auth.md @@ -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(); +} +```