Skip to content

Commit

Permalink
enable auth for the endpoints with support
Browse files Browse the repository at this point in the history
  • Loading branch information
jbolda committed Jan 6, 2023
1 parent c76e7bb commit 1869f85
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 26 deletions.
3 changes: 3 additions & 0 deletions app-config.production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ auth:
# see https://backstage.io/docs/auth/ to learn about auth providers
session:
secret: ${AUTH_SESSION_CLIENT_SECRET}
# see https://backstage.io/docs/auth/service-to-service-auth#setup
keys:
- secret: ${BACKEND_SECRET}
environment: production
providers:
auth0:
Expand Down
37 changes: 27 additions & 10 deletions packages/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,41 @@ import { GraphiQLPage } from '@backstage/plugin-graphiql';
import { SignInPage } from '@backstage/core-components';
import { auth0AuthApiRef } from './internal';
import Star from '@material-ui/icons/Star';
import {
discoveryApiRef,
useApi,
} from '@backstage/core-plugin-api';
import type { IdentityApi } from '@backstage/core-plugin-api';
import { setTokenCookie } from './cookieAuth';

const app = createApp({
apis,
components: {
SignInPage: props => (
<SignInPage
{...props}
providers={[
'guest',
{
SignInPage: props => {
const discoveryApi = useApi(discoveryApiRef);
return (
<SignInPage
{...props}
provider={{
id: 'auth0-auth-provider',
title: 'Auth0',
message: 'Sign in using Auth0',
apiRef: auth0AuthApiRef,
},
]}
/>
),
}}
onSignInSuccess={async (identityApi: IdentityApi) => {
// 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).
setTokenCookie(
await discoveryApi.getBaseUrl('cookie'),
identityApi,
);

props.onSignInSuccess(identityApi);
}}
/>
);
},
},
bindRoutes({ bind }) {
bind(catalogPlugin.externalRoutes, {
Expand Down
71 changes: 71 additions & 0 deletions packages/backend/src/authMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type { Config } from '@backstage/config';
import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node';
// this dep is required in @backstage/plugin-auth-node, but it doesn't export this functionality
// eslint-disable-next-line import/no-extraneous-dependencies
import { decodeJwt } from 'jose';
import { NextFunction, Request, Response, RequestHandler } from 'express';
import { URL } from 'url';
import { PluginEnvironment } from './types';

function setTokenCookie(
res: Response,
options: { token: string; secure: boolean; cookieDomain: string },
) {
try {
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,
path: '/',
httpOnly: true,
});
} catch (_err) {
// Ignore
}
}

export const createAuthMiddleware = async (
config: Config,
appEnv: PluginEnvironment,
) => {
const baseUrl = config.getString('backend.baseUrl');
const secure = baseUrl.startsWith('https://');
const cookieDomain = new URL(baseUrl).hostname;
const authMiddleware: RequestHandler = async (
req: Request,
res: Response,
next: NextFunction,
) => {
try {
const token =
getBearerTokenFromAuthorizationHeader(req.headers.authorization) ||
(req.cookies?.token as string | undefined);
if (!token) {
res.status(401).send('Unauthorized');
return;
}
try {
req.user = await appEnv.identity.getIdentity({ request: req });
} 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 && token !== req.cookies?.token) {
setTokenCookie(res, {
token,
secure,
cookieDomain,
});
}
next();
} catch (error) {
res.status(401).send('Unauthorized');
}
};
return authMiddleware;
};
35 changes: 19 additions & 16 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
/*
* Hi!
*
* Note that this is an EXAMPLE Backstage backend. Please check the README.
*
* Happy hacking!
*/

import Router from 'express-promise-router';
import {
createServiceBuilder,
Expand All @@ -22,6 +14,7 @@ import {
import { TaskScheduler } from '@backstage/backend-tasks';
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
import { createAuthMiddleware } from './authMiddleware';
import { Config } from '@backstage/config';
import app from './plugins/app';
import auth from './plugins/auth';
Expand All @@ -46,7 +39,7 @@ function makeCreateEnv(config: Config) {

const cacheManager = CacheManager.fromConfig(config);
const databaseManager = DatabaseManager.fromConfig(config, { logger: root });
const tokenManager = ServerTokenManager.noop();
const tokenManager = ServerTokenManager.fromConfig(config, { logger: root });
const taskScheduler = TaskScheduler.fromConfig(config);

const identity = DefaultIdentityClient.create({
Expand Down Expand Up @@ -100,18 +93,28 @@ async function main() {
const appEnv = useHotMemoize(module, () => createEnv('app'));
const humanitecEnv = useHotMemoize(module, () => createEnv('humanitec'));

const authMiddleware = await createAuthMiddleware(config, appEnv);
const apiRouter = Router();
apiRouter.use('/catalog', await catalog(catalogEnv));
apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
// The auth route must be publicly available as it is used during login
apiRouter.use('/auth', await auth(authEnv));
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
apiRouter.use('/proxy', await proxy(proxyEnv));
apiRouter.use('/search', await search(searchEnv));
apiRouter.use('/healthcheck', await healthcheck(healthcheckEnv));
// Add a simple endpoint to be used when setting a token cookie
apiRouter.use('/cookie', authMiddleware, (_req, res) => {
res.status(200).send(`Coming right up`);
});
// Only authenticated requests are allowed to the routes below
apiRouter.use('/catalog', authMiddleware, await catalog(catalogEnv));
apiRouter.use('/scaffolder', authMiddleware, await scaffolder(scaffolderEnv));
apiRouter.use('/techdocs', authMiddleware, await techdocs(techdocsEnv));
apiRouter.use('/proxy', authMiddleware, await proxy(proxyEnv));
apiRouter.use('/search', authMiddleware, await search(searchEnv));
apiRouter.use('/healthcheck', authMiddleware, await healthcheck(healthcheckEnv));
apiRouter.use('/effection-inspector', await effectionInspector(effectionInspectorEnv));
// apiRouter.use('/effection-inspector', authMiddleware, await effectionInspector(effectionInspectorEnv));
apiRouter.use('/humanitec', await humanitec(humanitecEnv));
// apiRouter.use('/humanitec', authMiddleware, await humanitec(humanitecEnv));
apiRouter.use('/graphql', await graphql(graphqlEnv));
apiRouter.use(notFoundHandler());
// apiRouter.use('/graphql', authMiddleware, await graphql(graphqlEnv));
apiRouter.use(authMiddleware, notFoundHandler());

const service = createServiceBuilder(module)
.loadConfig(config)
Expand Down

0 comments on commit 1869f85

Please sign in to comment.