@@ -2,4 +2,4 @@
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Added Google Cloud Identity Aware Proxy as an identiy Provider - based on aws-elb
|
||||
Added Google Cloud Identity-Aware Proxy as an identity provider.
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
id: provider
|
||||
title: Google Identity Aware Proxy Provider
|
||||
sidebar_label: Google IAP
|
||||
description:
|
||||
Adding Google Identity Aware Proxy as an authentication provider in Backstage
|
||||
# prettier-ignore
|
||||
description: Adding Google Identity-Aware Proxy as an authentication provider in Backstage
|
||||
---
|
||||
|
||||
# Using Google Identity Aware Proxy to authenticate requests
|
||||
# Using Google Identity-Aware Proxy to authenticate requests
|
||||
|
||||
Backstage allows offloading the responsibility of authenticating users to an
|
||||
Backstage allows offloading the responsibility of authenticating users to the
|
||||
Google HTTPS Load Balancer & [IAP](https://cloud.google.com/iap), leveraging the
|
||||
authentication support on IAP.
|
||||
authentication support on the latter.
|
||||
|
||||
This tutorial shows how to use authentication on an ALB sitting in front of
|
||||
Backstage.
|
||||
@@ -24,10 +24,11 @@ configured to serve the frontend app from the backend.
|
||||
|
||||
### Frontend
|
||||
|
||||
The Backstage App needs a SignInPage when authentication is required. When using
|
||||
IAP Proxy 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.
|
||||
The Backstage App needs a `SignInPage` to be configured. When using IAP Proxy
|
||||
authentication Backstage will only be loaded once the user has successfully
|
||||
authenticated; we won't need to display a sign-in page as such, however we will
|
||||
need to create a dummy `SignInPage` component that can decode and refresh the
|
||||
token.
|
||||
|
||||
- edit `packages/app/src/App.tsx`
|
||||
- import the following two additional definitions from `@backstage/core`:
|
||||
@@ -52,6 +53,7 @@ const refreshToken = async ({ props, discoveryApiConfig, config }) => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const request = await fetch(`${baseUrl}/gcp-iap/refresh`, {
|
||||
headers: {
|
||||
@@ -76,6 +78,7 @@ const refreshToken = async ({ props, discoveryApiConfig, config }) => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const DummySignInComponent: any = (props: any) => {
|
||||
try {
|
||||
const config = useApi(configApiRef);
|
||||
@@ -94,7 +97,7 @@ When using ALB auth it is not possible to leverage the built-in auth config
|
||||
discovery mechanism implemented in the app created by default; bespoke logic
|
||||
needs to be implemented.
|
||||
|
||||
- replace the content of `packages/backend/plugin/auth.ts` with the below
|
||||
- Replace the content of `packages/backend/plugin/auth.ts` with the below
|
||||
|
||||
```ts
|
||||
// imports are relative - as this was tested out in repo directly
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"express-session": "^1.17.1",
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"fs-extra": "9.1.0",
|
||||
>>>>>>> 4d5fd11228 (first minor cleanups)
|
||||
"google-auth-library": "^7.2.0",
|
||||
"helmet": "^4.0.0",
|
||||
"jose": "^1.27.1",
|
||||
|
||||
@@ -13,5 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { createGcpIAPProvider } from './provider';
|
||||
export type { GcpIAPProviderOptions } from './provider';
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import express from 'express';
|
||||
|
||||
import { GcpIAPProvider } from './provider';
|
||||
import { AuthResponse } from '../types';
|
||||
import { GcpIAPProvider } from './provider';
|
||||
|
||||
jest.mock('google-auth-library');
|
||||
|
||||
@@ -57,22 +57,22 @@ describe('GcpIAPProvider', () => {
|
||||
getEntityByName: jest.fn(),
|
||||
};
|
||||
|
||||
const mockRequest = ({
|
||||
const mockRequest = {
|
||||
header: jest.fn(() => {
|
||||
return 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImZvbyIsImlzcyI6ImZvbyJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.T2BNS4G-6RoiFnXc8Q8TiwdWzTpNitY8jcsGM3N3-Yo';
|
||||
}),
|
||||
} as unknown) as express.Request;
|
||||
const mockRequestWithoutJwt = ({
|
||||
} as unknown as express.Request;
|
||||
const mockRequestWithoutJwt = {
|
||||
header: jest.fn(() => {
|
||||
return undefined;
|
||||
}),
|
||||
} as unknown) as express.Request;
|
||||
const mockResponse = ({
|
||||
} as unknown as express.Request;
|
||||
const mockResponse = {
|
||||
end: jest.fn(),
|
||||
header: () => jest.fn(),
|
||||
json: jest.fn().mockReturnThis(),
|
||||
status: jest.fn(),
|
||||
} as unknown) as express.Response;
|
||||
} as unknown as express.Response;
|
||||
|
||||
describe('should transform to type OAuthResponse', () => {
|
||||
it('when JWT is valid and identity is resolved successfully', async () => {
|
||||
|
||||
@@ -14,19 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import express from 'express';
|
||||
import { OAuth2Client } from 'google-auth-library';
|
||||
import { Logger } from 'winston';
|
||||
import {
|
||||
AuthProviderRouteHandlers,
|
||||
AuthProviderFactory,
|
||||
AuthProviderFactoryOptions,
|
||||
AuthProviderRouteHandlers,
|
||||
ExperimentalIdentityResolver,
|
||||
} from '../types';
|
||||
|
||||
import express from 'express';
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
|
||||
import { OAuth2Client } from 'google-auth-library';
|
||||
|
||||
const IAP_JWT_HEADER = 'x-goog-iap-jwt-assertion';
|
||||
|
||||
export type GcpIAPProviderOptions = {
|
||||
@@ -47,6 +45,7 @@ export class GcpIAPProvider implements AuthProviderRouteHandlers {
|
||||
this.catalogClient = catalogClient;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
frameHandler(): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
@@ -102,7 +101,9 @@ export class GcpIAPProvider implements AuthProviderRouteHandlers {
|
||||
}
|
||||
}
|
||||
|
||||
export const createGcpIAPProvider = (_options?: GcpIAPProviderOptions) => {
|
||||
export function createGcpIapProvider(
|
||||
_options?: GcpIAPProviderOptions,
|
||||
): AuthProviderFactory {
|
||||
return ({
|
||||
logger,
|
||||
catalogApi,
|
||||
@@ -111,13 +112,13 @@ export const createGcpIAPProvider = (_options?: GcpIAPProviderOptions) => {
|
||||
}: AuthProviderFactoryOptions) => {
|
||||
const audience = config.getString('audience');
|
||||
if (identityResolver !== undefined) {
|
||||
return new GcpIAPProvider(logger, catalogApi, {
|
||||
audience,
|
||||
identityResolutionCallback: identityResolver,
|
||||
});
|
||||
throw new Error(
|
||||
'Identity resolver is required to use this authentication provider',
|
||||
);
|
||||
}
|
||||
throw new Error(
|
||||
'Identity resolver is required to use this authentication provider',
|
||||
);
|
||||
return new GcpIAPProvider(logger, catalogApi, {
|
||||
audience,
|
||||
identityResolutionCallback: identityResolver,
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user