Update authenticate-api-requests contrib page.

This comes with a few adjustments to newer versions of the dependency packages.
Also some cleanup, and adding a server-to-server auth path.

Signed-off-by: Axel Hecht <axel@pike.org>
This commit is contained in:
Axel Hecht
2022-10-04 19:04:30 +02:00
parent 037b4393c4
commit 42f39cca99
@@ -8,30 +8,28 @@ API requests from frontend plugins include an authorization header with a Backst
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).
Create `packages/backend/src/authMiddleware.ts`:
```typescript
// packages/backend/src/index.ts from a create-app deployment
import cookieParser from 'cookie-parser';
import { Request, Response, NextFunction } from 'express';
import { JWT } from 'jose';
import { URL } from 'url';
import { SingleHostDiscovery } from '@backstage/backend-common';
import type { Config } from '@backstage/config';
import {
IdentityClient,
getBearerTokenFromAuthorizationHeader,
IdentityClient,
} from '@backstage/plugin-auth-node';
// ...
import { NextFunction, Request, Response, RequestHandler } from 'express';
import { decodeJwt } from 'jose';
import { URL } from 'url';
import { PluginEnvironment } from './types';
function setTokenCookie(
res: Response,
options: { token: string; secure: boolean; cookieDomain: string },
) {
try {
const payload = JWT.decode(options.token) as object & {
exp: number;
};
res.cookie(`token`, options.token, {
expires: new Date(payload?.exp ? payload?.exp * 1000 : 0),
const payload = decodeJwt(options.token);
res.cookie('token', options.token, {
expires: new Date(payload.exp ? payload.exp * 1000 : 0),
secure: options.secure,
sameSite: 'lax',
domain: options.cookieDomain,
@@ -43,9 +41,10 @@ function setTokenCookie(
}
}
async function main() {
// ...
export const createAuthMiddleware = async (
config: Config,
appEnv: PluginEnvironment,
) => {
const discovery = SingleHostDiscovery.fromConfig(config);
const identity = IdentityClient.create({
discovery,
@@ -54,7 +53,7 @@ async function main() {
const baseUrl = config.getString('backend.baseUrl');
const secure = baseUrl.startsWith('https://');
const cookieDomain = new URL(baseUrl).hostname;
const authMiddleware = async (
const authMiddleware: RequestHandler = async (
req: Request,
res: Response,
next: NextFunction,
@@ -62,13 +61,21 @@ async function main() {
try {
const token =
getBearerTokenFromAuthorizationHeader(req.headers.authorization) ||
req.cookies['token'];
req.user = await identity.authenticate(token);
(req.cookies.token as string | undefined);
if (!token) {
res.status(401).send('Unauthorized');
return;
}
try {
req.user = await identity.authenticate(token);
} catch {
await appEnv.tokenManager.authenticate(token);
}
if (!req.headers.authorization) {
// Authorization header may be forwarded by plugin requests
req.headers.authorization = `Bearer ${token}`;
}
if (token !== req.cookies['token']) {
if (token && token !== req.cookies.token) {
setTokenCookie(res, {
token,
secure,
@@ -77,9 +84,24 @@ async function main() {
}
next();
} catch (error) {
res.status(401).send(`Unauthorized`);
res.status(401).send('Unauthorized');
}
};
return authMiddleware;
};
```
```typescript
// packages/backend/src/index.ts from a create-app deployment
import { createAuthMiddleware } from './authMiddleware';
// ...
async function main() {
// ...
const authMiddleware = await createAuthMiddleware(config, appEnv);
const apiRouter = Router();
apiRouter.use(cookieParser());
@@ -99,12 +121,10 @@ async function main() {
}
```
Create `packages/app/src/cookieAuth.ts`:
```typescript
// packages/app/src/App.tsx from a create-app deployment
import { discoveryApiRef, useApi } from '@backstage/core-plugin-api';
// ...
import type { IdentityApi } from '@backstage/core-plugin-api';
// Parses supplied JWT token and returns the payload
function parseJwt(token: string): { exp: number } {
@@ -113,9 +133,11 @@ function parseJwt(token: string): { exp: number } {
const jsonPayload = decodeURIComponent(
atob(base64)
.split('')
.map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
})
.map(
c =>
// eslint-disable-next-line prefer-template
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2),
)
.join(''),
);
@@ -132,7 +154,7 @@ function msUntilExpiry(token: string): number {
// Calls the specified url regularly using an auth token to set a token cookie
// to authorize regular HTTP requests when loading techdocs
async function setTokenCookie(url: string, identityApi: IdentityApi) {
export async function setTokenCookie(url: string, identityApi: IdentityApi) {
const { token } = await identityApi.getCredentials();
if (!token) {
return;
@@ -155,6 +177,14 @@ async function setTokenCookie(url: string, identityApi: IdentityApi) {
ms > 0 ? ms : 10000,
);
}
```
```typescript
// packages/app/src/App.tsx from a create-app deployment
import { setTokenCookie } from './cookieAuth';
// ...
const app = createApp({
// ...