From 74534a0f8cdd09f23e2f781af99ffd5813f1f13c Mon Sep 17 00:00:00 2001 From: Erik Larsson Date: Sat, 20 Feb 2021 01:46:00 +0100 Subject: [PATCH] Move auth token to contrib --- .../tutorials/authenticate-api-requests.md | 39 +++++++++++++++++- .../src/lib/oauth/OAuthAdapter.test.ts | 9 +---- .../src/lib/oauth/OAuthAdapter.ts | 40 ------------------- .../techdocs-backend/src/service/router.ts | 9 ++--- plugins/techdocs/src/api.ts | 1 + 5 files changed, 44 insertions(+), 54 deletions(-) diff --git a/contrib/docs/tutorials/authenticate-api-requests.md b/contrib/docs/tutorials/authenticate-api-requests.md index 4698293152..4f4547a49e 100644 --- a/contrib/docs/tutorials/authenticate-api-requests.md +++ b/contrib/docs/tutorials/authenticate-api-requests.md @@ -13,10 +13,33 @@ Caveat: as of writing this, Backstage does not refresh the identity token so eve import cookieParser from 'cookie-parser'; import { Request, Response, NextFunction } from 'express'; +import { JWT } from 'jose'; +import { URL } from 'url'; import { IdentityClient } from '@backstage/plugin-auth-backend'; // ... +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), + secure: options.secure, + sameSite: 'lax', + domain: options.cookieDomain, + path: '/', + httpOnly: true, + }); + } catch (_err) { + // Ignore + } +} + async function main() { // ... @@ -25,6 +48,9 @@ async function main() { discovery, issuer: await discovery.getExternalBaseUrl('auth'), }); + const baseUrl = config.getString('backend.baseUrl'); + const secure = baseUrl.startsWith('https://'); + const cookieDomain = new URL(baseUrl).hostname; const authMiddleware = async ( req: Request, res: Response, @@ -33,8 +59,19 @@ async function main() { try { const token = IdentityClient.getBearerToken(req.headers.authorization) || - req.cookies['access-token']; + req.cookies['token']; req.user = await identity.authenticate(token); + if (!req.headers.authorization) { + // Authorization header may be forwarded by plugin requests + req.headers.authorization = `Bearer ${token}`; + } + if (token !== req.cookies['token']) { + setTokenCookie(res, { + token, + secure, + cookieDomain, + }); + } next(); } catch (error) { res.status(401).send(`Unauthorized`); diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts index 7d51f152f8..d2b31213f2 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts @@ -160,7 +160,7 @@ describe('OAuthAdapter', () => { expect(mockResponse.cookie).toHaveBeenCalledTimes(0); }); - it('removes access and refresh cookies when logging out', async () => { + it('removes refresh cookie when logging out', async () => { const oauthProvider = new OAuthAdapter(providerInstance, { ...oAuthProviderOptions, disableRefresh: false, @@ -176,12 +176,7 @@ describe('OAuthAdapter', () => { } as unknown) as express.Response; await oauthProvider.logout(mockRequest, mockResponse); - expect(mockResponse.cookie).toHaveBeenCalledTimes(2); - expect(mockResponse.cookie).toHaveBeenCalledWith( - expect.stringContaining('access-token'), - '', - expect.objectContaining({ path: '/api' }), - ); + expect(mockResponse.cookie).toHaveBeenCalledTimes(1); expect(mockResponse.cookie).toHaveBeenCalledWith( expect.stringContaining('test-provider-refresh-token'), '', diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts index 02e4e26091..62cb8e444d 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts @@ -16,7 +16,6 @@ import express from 'express'; import crypto from 'crypto'; -import { JWT } from 'jose'; import { URL } from 'url'; import { AuthProviderRouteHandlers, @@ -128,9 +127,6 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { } await this.populateIdentity(response.backstageIdentity); - if (response.backstageIdentity?.idToken) { - this.setAccessTokenCookie(res, response.backstageIdentity?.idToken); - } // post message back to popup if successful return postMessageResponse(res, this.options.appOrigin, { @@ -155,7 +151,6 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { return; } - this.removeAccessTokenCookie(res); if (!this.options.disableRefresh) { // remove refresh token cookie before logout this.removeRefreshTokenCookie(res); @@ -195,9 +190,6 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { ); await this.populateIdentity(response.backstageIdentity); - if (response.backstageIdentity?.idToken) { - this.setAccessTokenCookie(res, response.backstageIdentity?.idToken); - } if ( response.providerInfo.refreshToken && @@ -254,27 +246,6 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { return req.cookies[`${providerId}-scope`]; }; - private setAccessTokenCookie = ( - res: express.Response, - accessToken: string, - ) => { - try { - const payload = JWT.decode(accessToken) as object & { - exp: number; - }; - res.cookie(`access-token`, accessToken, { - expires: new Date(payload?.exp ? payload?.exp * 1000 : 0), - secure: this.options.secure, - sameSite: 'lax', - domain: this.options.cookieDomain, - path: '/api', - httpOnly: true, - }); - } catch (_err) { - // Ignore - } - }; - private setRefreshTokenCookie = ( res: express.Response, refreshToken: string, @@ -289,17 +260,6 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { }); }; - private removeAccessTokenCookie = (res: express.Response) => { - res.cookie(`access-token`, '', { - maxAge: 0, - secure: this.options.secure, - sameSite: 'lax', - domain: this.options.cookieDomain, - path: '/api', - httpOnly: true, - }); - }; - private removeRefreshTokenCookie = (res: express.Response) => { res.cookie(`${this.options.providerId}-refresh-token`, '', { maxAge: 0, diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index 885a1a64af..d300400c70 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -82,9 +82,7 @@ export async function createRouter({ const { kind, namespace, name } = req.params; try { - const token = - getBearerToken(req.headers.authorization) || - req.cookies['access-token']; + const token = getBearerToken(req.headers.authorization); const entity = (await ( await fetch( `${catalogUrl}/entities/by-name/${kind}/${namespace}/${name}`, @@ -115,8 +113,7 @@ export async function createRouter({ const catalogUrl = await discovery.getBaseUrl('catalog'); const triple = [kind, namespace, name].map(encodeURIComponent).join('/'); - const token = - getBearerToken(req.headers.authorization) || req.cookies['access-token']; + const token = getBearerToken(req.headers.authorization); const catalogRes = await fetch(`${catalogUrl}/entities/by-name/${triple}`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, }); @@ -214,5 +211,5 @@ export async function createRouter({ } function getBearerToken(header?: string): string | undefined { - return header?.match(/(?:Bearer|Basic)\s+(\S+)/i)?.[1]; + return header?.match(/(?:Bearer)\s+(\S+)/i)?.[1]; } diff --git a/plugins/techdocs/src/api.ts b/plugins/techdocs/src/api.ts index 6de94b63bb..ea176f26eb 100644 --- a/plugins/techdocs/src/api.ts +++ b/plugins/techdocs/src/api.ts @@ -171,6 +171,7 @@ export class TechDocsStorageApi implements TechDocsStorage { const request = await fetch( `${url.endsWith('/') ? url : `${url}/`}index.html`, { + credentials: 'include', headers: token ? { Authorization: `Bearer ${token}` } : {}, }, );