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:
@@ -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({
|
||||
// ...
|
||||
|
||||
Reference in New Issue
Block a user