This project enables Google Groups-controlled access to API Gateway endpoints. You can create a API Gateway custom authorizer that reads a user's email address from an OAuth token and makes a call to the Directory API to check that the user is a member of a set of predefined groups.
An example use case would be a client-side web app using Google Sign-In for Websites backed by API Gateway. With the custom authorizer provided by this repository, the client can pass the ID token generated by Google Sign-In to the authorizer function, granting access to the API if group membership requirements are satisfied.
More information about the way the authorizer receives Google ID can be found here.
There are a few things to set up in order for all of this to work. You'll need:
- enable the Admin SDK API
- create a service account
- generate a P12 key from the above service account and upload it to S3
- add the
admin.directory.group.readonly
scope to the service account (you'll need to ask a G Suite administrator/IT support)
- create an object that extends
RequestStreamHandler
and wraps an instance ofGoogleDirectoryLambdaAuthorizer
...
object MyCustomAuthorizer extends RequestStreamHandler {
def initAuthorizer(): Either[Throwable, GoogleDirectoryLambdaAuthorizer] =
GoogleDirectoryLambdaAuthorizer.forServiceAccount(
applicationName = "My great application",
requiredGroups = Set("powerusers@guardian.co.uk", "security@guardian.co.uk"),
oauthClientId = ClientId("abc123"),
s3Client = amazonS3Client,
p12KeyPath = S3Path("private-bucket", "credentials.p12"),
serviceAccountId = EmailAddress("service@myapp.iam.gserviceaccount.com"),
userToImpersonate = EmailAddress("admin@guardian.co.uk")
)
override def handleRequest(input: InputStream, output: OutputStream, context: Context): Unit =
initAuthorizer().fold(
err => throw err,
_.handleRequest(input, output, context)
)
}
...and create a lambda that uses this object for its handler. Note the S3Path
must contain the key uploaded from step 1, with read access for the lambda.
Create an authorizer using the function from 2. in your API. For example, with the CLI:
aws apigateway create-authorizer --rest-api-id 1234123412 \
--name 'GoogleDirectoryAuthorizer' \
--type TOKEN \
--authorizer-uri 'arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-1:123412341234:function:customAuthFunction/invocations' \
--identity-source 'method.request.header.Authorization' \
--identity-validation-expression 'Bearer (.*)' \
--authorizer-result-ttl-in-seconds 0
Note that the authorizer must be of TOKEN
type and you must disable caching (TTL 0) since the method ARN is included in the policy returned by the authorizer.
You can now send requests to your API including an Authorization
header in the requests:
Authorization: Bearer YOUR_TOKEN_HERE
This is an example template (using AWS SAM) that defines an API with a single method, function, and the custom authorizer defined above.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
SampleAPI:
Type: AWS::Serverless::Api
Properties:
Name: Sample_API
StageName: PROD
Cors:
AllowMethods: "'*'"
AllowHeaders: "'*'"
AllowOrigin: "'*'"
DefinitionBody:
swagger: "2.0"
securityDefinitions:
googleAuthorizer:
type: apiKey
name: Authorization
in: header
x-amazon-apigateway-authtype: oauth2
x-amazon-apigateway-authorizer:
authorizerUri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GoogleDirectoryAuthorizer.Arn}/invocations
identitySource: method.request.header.Authorization
identityValidationExpression: "Bearer (.*)"
name: Google OAuth
type: TOKEN
info:
title:
Fn::Sub: Sample API
description: API to demonstrate custom Google Directory authorizer
paths:
/ping:
get:
summary: ping
responses:
'204':
description: OK
security:
- googleAuthorizer: []
x-amazon-apigateway-integration:
httpMethod: POST
type: aws_proxy
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Ping.Arn}/invocations
responses:
'204':
statusCode: 204
GoogleDirectoryAuthorizer:
Type: AWS::Serverless::Function
Properties:
FunctionName: google-directory-authorizer
Description: Authorize API Gateway requests
Handler: com.gu.demo.MyCustomAuthorizer::handleRequest
Runtime: java8
CodeUri: target/scala-2.12/sample.jar
Policies:
- S3ReadPolicy:
BucketName: private-bucket
Ping:
Type: AWS::Serverless::Function
Properties:
FunctionName: ping
Description: ping
Handler: com.gu.demo.Ping::handleRequest
Runtime: java8
CodeUri: target/scala-2.12/sample.jar