diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md new file mode 100644 index 0000000000..9dab72fe66 --- /dev/null +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -0,0 +1,179 @@ +# Using AWS Application Load Balancer with Azure Active Directory to authenticate requests + +Backstage allows to offload the responsibility of authenticating users to AWS Application Loadbalancer (**ALB**), leveraging the authentication support on ALB. +This tutorial shows how to use authentication on an ALB sitting in front of Backstage. +Azure Active Directory (**AAD**) is used as identity provider but any identity provider supporting OpenID Connect (OIDC) can be used. + +It is assumed an ALB is already serving traffic in front of a Backstage instance configured to serve the frontend app from the backend. + +## Infrastructure setup + +### AAD App + +The AAD App is used to execute the authentication flow, serve and refresh the identity token. + +Create the AAD App following the steps outlined in `Create a Microsoft App Registration in Microsoft Portal` section from the tutorial [Monorepo App Setup With Authentication][monorepo-app-setup-with-auth]. + +Instead of `localhost` addresses, use the following values. + +- Identifier URI: `https://backstage.yourdomain.com` +- Redirect URI: `https://backstage.yourdomain.com/oauth2/idpresponse` + +`Application (client) Id`, `Directory (tenant) ID` and `client secret`values will be used while configuring the ALB. + +### ALB + +In AWS console configure ALB Authentication as described below: + +- Edit the ALB rule used to forward the traffic to Backstage and add a new `Authenticate` action. The action will have higher priority compared to the existing `Forward to`. +- Select `OIDC` under `Authenticate` +- Set `Issuer` to `https://login.microsoftonline.com/{TENANT_ID}/v2.0`, replacing `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App. +- Set `Authorization endpoint` to `https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize`, replacing `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App. +- Set `Token endpoint` to `https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize`, replacing `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App. +- Set `User info endpoint` to `https://graph.microsoft.com/oidc/userinfo` +- Set `Client ID` to the AAD App `Application (client) Id` +- Set `Client secret` to the AAD APP `client secret` + +Use the following advanced settings: + +- `Session cookie name` = `AWSELBAuthSessionCookie` +- `Session timeout` = `604800` seconds +- `Scope` = `openid profile offline_access` +- `Action on unauthenticated request` = `Autenticate (client reattempt)` + +Once you've saved the action, you should see an authentication flow be triggered against AAD when visiting Backstage address at `https://backstage.yourdomain.com`. The flow will not complete successfully as Backstage app isn't yet configured properly. + +## Backstage changes + +### Frontend + +The Backstage App needs a SingInPage when authentication is required. +When using ALB authentication Backstage will only be loaded once the user has successfully authenticated; we won't need to display a SignIn page, however we will need to create a dummy SignIn component that can refresh the token. + +- edit `packages/app/src/App.tsx` +- import the following two additional definitions from `@backstage/core`: `useApi`, `configApiRef`; these will be used to check wether backstage is running locally or behind an ALB +- add the following definition just before the app is created (`const app = createApp`): + +```ts +const DummySignInComponent: any = (props: any) => { + const config = useApi(configApiRef); + const shouldAuth = !!config.getOptionalConfig('auth.providers.awsalb'); + if (shouldAuth) { + fetch(`${window.location.origin}/api/auth/awsalb/refresh`) + .then(data => data.json()) + .then(data => { + props.onResult({ + userId: data.backstageIdentity.id, + profile: data.profile, + }); + }); + } else { + // when running locally we default user identity to `Local User` + props.onResult({ + userId: 'local user', + profile: { + email: 'local.user@yourdomain.com', + displayName: 'Local User', + picture: '', + }, + }); + } + + return
; +}; +``` + +- use `DummySingInComponent` as `SignInPage` by changing `createApp` statement as follow: + +```ts +const app = createApp({ + apis, + plugins: Object.values(plugins), + components: { + SignInPage: DummySignInComponent, + }, + bindRoutes({ bind }) { + bind(catalogPlugin.externalRoutes, { + createComponent: scaffolderPlugin.routes.root, + }); + bind(apiDocsPlugin.externalRoutes, { + createComponent: scaffolderPlugin.routes.root, + }); + }, +}); +``` + +### Backend + +When using ALB auth it is not possible to leverage the built-in auth config discovery mechanism implemented in the app created by default: a bespoke logic needs to be implemented. + +- replace the content of `packages/backend/plugin/auth.ts` with the below + +```ts +import { + createRouter, + AuthResponse, + AuthProviderFactoryOptions, + defaultAuthProviderFactories, +} from '@backstage/plugin-auth-backend'; +import { PluginEnvironment } from '../types'; + +export default async function createPlugin({ + logger, + database, + config, + discovery, +}: PluginEnvironment) { + const identityResolver = (payload: any): Promise