-
Notifications
You must be signed in to change notification settings - Fork 2.4k
feat(auth): add support for API Gateway Authorizers #546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
cd28c89
feat(auth): add support for API Gateway Authorizers
brettstack 8cb9f63
addressed my own feedback in GitHub comments
brettstack 915760c
improvement: remove check for Auth dict since it's verified by proper…
brettstack 0ea0ce1
test: add error tests for authorizers
brettstack f4ecc38
test: fix missing DefaultAuthorizer in Authorizers test
brettstack e451739
fix: add permissions for API to invoke Authorizer Lambda Function
brettstack 18ecefc
fix: fix error when no Auth defined
brettstack ea7efc8
docs: add Lambda REQUEST Authorizer example
brettstack e0b412f
fix: add error handling when no identity source provided for Lambda R…
brettstack da1867e
docs(examples): improve README commands for api_lambda_request_auth
brettstack 3998447
add API Gateway + Cognito Authorizer example
brettstack 60671cc
simplify setup by using npm scripts
brettstack e9d6c25
update to use authorization_code flow
brettstack 9872796
add tests for cognito authorizers
brettstack 1d1be1c
merge upstream/develop
brettstack 90b3a39
convert result of map() to list for py3 support
brettstack 6dca4c7
update spec to include API Auth and Function Auth
brettstack c28ae1a
add TOC for Data Types in spec
brettstack 847646d
address documentation change requests
brettstack f6ac94b
address PR change requests
brettstack File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
extends: standard | ||
rules: | ||
prefer-promise-reject-errors: off # API Gateway expects string response from Lamdba (when using async + Promise.reject) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# API Gateway + Cognito Auth + Cognito Hosted Auth Example | ||
|
||
This example shows you how to create an API Gateway API with a Cognito Authorizer using SAM. | ||
|
||
## Running the example | ||
|
||
Install the Node.js/NPM dependencies for your API's Lambda logic into the `src/` directory. This is necessary so that the dependencies get packaged up along with your Lambda function. | ||
|
||
```bash | ||
npm install . --prefix ./src | ||
``` | ||
|
||
Deploy the example into your account (replace `YOUR_S3_ARTIFACTS_BUCKET` with an existing S3 bucket to store your app assets): | ||
|
||
```bash | ||
# The following default values are also allowed: STACK_NAME, COGNITO_USER_POOL_CLIENT_NAME, COGNITO_USER_POOL_DOMAIN_PREFIX | ||
S3_BUCKET_NAME=YOUR_S3_ARTIFACTS_BUCKET \ | ||
npm run package-deploy | ||
``` | ||
|
||
Cognito User Pools doesn't currently have CloudFormation support for configuring their Hosted Register/Signin UI. For now we will create these via the AWS CLI: | ||
|
||
```bash | ||
npm run configure-cognito-user-pool | ||
``` | ||
|
||
Open the registration page created and hosted for you by Cognito in your browser. After the page loads, enter a Username and Password and click the Sign Up button. | ||
|
||
```bash | ||
npm run open-signup-page | ||
|
||
# Alternatively, you can open the login page by running `npm run open-login-page` | ||
``` | ||
|
||
After clicking Sign Up, you will be redirected to the UI client for your API. | ||
|
||
To access the API UI directly as an unauthorized user (who has access to `GET /users` and `GET /users/{userId}`) you can run `npm run open-api-ui`. | ||
|
||
## Additional resources | ||
|
||
- https://aws.amazon.com/blogs/aws/launch-amazon-cognito-user-pools-general-availability-app-integration-and-federation/ | ||
- https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html | ||
- https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-idp-settings.html | ||
- https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-invoke-api-integrated-with-cognito-user-pool.html | ||
- https://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"name": "api_cognito_auth", | ||
"version": "1.0.0", | ||
"description": "Example using API Gateway with Cognito Authorizer.", | ||
"main": "lambda.js", | ||
"license": "Apache-2.0", | ||
"dependencies": { | ||
"aws-serverless-express": "^3.3.3", | ||
"body-parser": "^1.17.1", | ||
"cors": "^2.8.3", | ||
"express": "^4.15.2", | ||
"pug": "^2.0.0-rc.1" | ||
}, | ||
"scripts": { | ||
"package-deploy": "npm run set-config && npm run package && npm run deploy", | ||
"set-config": "npm config set STACK_NAME ${STACK_NAME:-sam-example-api-cognito-auth}", | ||
"package": "aws cloudformation package --template-file template.yaml --output-template-file template.packaged.yaml --s3-bucket $S3_BUCKET_NAME", | ||
"deploy": "aws cloudformation deploy --template-file ./template.packaged.yaml --stack-name $STACK_NAME --capabilities CAPABILITY_IAM", | ||
"configure-cognito-user-pool": "npm run set-cognito-user-pool-id && npm run set-cognito-user-pool-client-id && npm run set-api-id && npm run set-api-url && npm run update-user-pool-client && npm run create-user-pool-domain", | ||
"set-cognito-user-pool-id": "npm config set COGNITO_USER_POOL_ID $(aws cloudformation describe-stacks --stack-name $(npm config get STACK_NAME) --query 'Stacks[].Outputs[?OutputKey==`CognitoUserPoolId`].OutputValue' --output text)", | ||
"set-cognito-user-pool-client-id": "npm config set COGNITO_USER_POOL_CLIENT_ID $(aws cloudformation describe-stacks --stack-name $(npm config get STACK_NAME) --query 'Stacks[].Outputs[?OutputKey==`CognitoUserPoolClientId`].OutputValue' --output text)", | ||
"set-api-url": "npm config set API_URL $(aws cloudformation describe-stacks --stack-name sam-example-api-cognito-auth --query 'Stacks[].Outputs[?OutputKey==`ApiUrl`].OutputValue' --output text)", | ||
"set-api-id": "npm config set API_ID $(aws cloudformation describe-stacks --stack-name sam-example-api-cognito-auth --query 'Stacks[].Outputs[?OutputKey==`ApiId`].OutputValue' --output text)", | ||
"update-user-pool-client": "aws cognito-idp update-user-pool-client --user-pool-id $(npm config get COGNITO_USER_POOL_ID) --client-id $(npm config get COGNITO_USER_POOL_CLIENT_ID) --supported-identity-providers COGNITO --callback-urls \"[\\\"$(npm config get API_URL)\\\"]\" --allowed-o-auth-flows code implicit --allowed-o-auth-scopes openid email --allowed-o-auth-flows-user-pool-client", | ||
"create-user-pool-domain": "aws cognito-idp create-user-pool-domain --domain $(npm config get API_ID) --user-pool-id $(npm config get COGNITO_USER_POOL_ID)", | ||
"open-signup-page": "open \"https://$(npm config get API_ID).auth.us-east-1.amazoncognito.com/signup?response_type=code&client_id=$(npm config get COGNITO_USER_POOL_CLIENT_ID)&redirect_uri=$(npm config get API_URL)\"", | ||
"open-login-page": "open \"https://$(npm config get API_ID).auth.us-east-1.amazoncognito.com/login?response_type=code&client_id=$(npm config get COGNITO_USER_POOL_CLIENT_ID)&redirect_uri=$(npm config get API_URL)\"", | ||
"open-api-ui": "open \"$(npm config get API_URL)\"" | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
'use strict' | ||
const express = require('express') | ||
const bodyParser = require('body-parser') | ||
const cors = require('cors') | ||
const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware') | ||
const app = express() | ||
const router = express.Router() | ||
|
||
app.set('view engine', 'pug') | ||
|
||
router.use(cors()) | ||
router.use(bodyParser.json()) | ||
router.use(bodyParser.urlencoded({ extended: true })) | ||
router.use(awsServerlessExpressMiddleware.eventContext()) | ||
|
||
router.get('/', (req, res) => { | ||
res.render('index', { | ||
apiId: req.apiGateway ? req.apiGateway.event.requestContext.apiId : null, | ||
apiUrl: req.apiGateway ? `https://${req.apiGateway.event.headers.Host}/${req.apiGateway.event.requestContext.stage}` : 'http://localhost:3000', | ||
cognitoUserPoolClientId: process.env.COGNITO_USER_POOL_CLIENT_ID | ||
}) | ||
}) | ||
|
||
router.get('/users', (req, res) => { | ||
res.json(users) | ||
}) | ||
|
||
router.get('/users/:userId', (req, res) => { | ||
const user = getUser(req.params.userId) | ||
|
||
if (!user) return res.status(404).json({}) | ||
|
||
return res.json(user) | ||
}) | ||
|
||
router.post('/users', (req, res) => { | ||
const user = { | ||
id: ++userIdCounter, | ||
name: req.body.name | ||
} | ||
users.push(user) | ||
res.status(201).json(user) | ||
}) | ||
|
||
router.put('/users/:userId', (req, res) => { | ||
const user = getUser(req.params.userId) | ||
|
||
if (!user) return res.status(404).json({}) | ||
|
||
user.name = req.body.name | ||
res.json(user) | ||
}) | ||
|
||
router.delete('/users/:userId', (req, res) => { | ||
const userIndex = getUserIndex(req.params.userId) | ||
|
||
if (userIndex === -1) return res.status(404).json({}) | ||
|
||
users.splice(userIndex, 1) | ||
res.json(users) | ||
}) | ||
|
||
const getUser = (userId) => users.find(u => u.id === parseInt(userId)) | ||
const getUserIndex = (userId) => users.findIndex(u => u.id === parseInt(userId)) | ||
|
||
// Ephemeral in-memory data store | ||
const users = [{ | ||
id: 1, | ||
name: 'Joe' | ||
}, { | ||
id: 2, | ||
name: 'Jane' | ||
}] | ||
let userIdCounter = users.length | ||
|
||
app.use('/', router) | ||
|
||
// Export your express server so you can import it in the lambda function. | ||
module.exports = app |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
'use strict' | ||
const awsServerlessExpress = require('aws-serverless-express') | ||
const app = require('./app') | ||
|
||
const server = awsServerlessExpress.createServer(app) | ||
|
||
exports.handler = (event, context) => awsServerlessExpress.proxy(server, event, context) |
159 changes: 159 additions & 0 deletions
159
examples/2016-10-31/api_cognito_auth/src/views/index.pug
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
doctype html | ||
html | ||
head | ||
title My Serverless Application | ||
style. | ||
body { | ||
width: 650px; | ||
margin: auto; | ||
} | ||
h1 { | ||
text-align: center; | ||
} | ||
.response > code { | ||
display: block; | ||
background-color: #eff0f1; | ||
color: #393318; | ||
padding: 5px; | ||
font-family: Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,sans-serif; | ||
white-space: pre; | ||
overflow-x: auto; | ||
} | ||
form { | ||
margin-bottom: 1rem; | ||
} | ||
.form-group { | ||
padding-bottom: 1rem; | ||
} | ||
label { | ||
display: block; | ||
} | ||
body | ||
h1 My Serverless Application | ||
p | ||
| Public endpoints: GET /, GET /users, GET /users/{userId} | ||
p | ||
| Authorized endpoints: POST /users, PUT /users/{userId}, DELETE /users/{userId} | ||
|
||
section.form | ||
h2 Invoke API | ||
p Experiment with the `users` resource with the form below. | ||
form | ||
div.form-group | ||
label(for='methodField') Method | ||
select(name='method' id='methodField') | ||
option(value='GET') GET | ||
option(value='POST') POST | ||
option(value='PUT') PUT | ||
option(value='DELETE') DELETE | ||
div.form-group | ||
label(for='idField') user id | ||
input(type='text' name='id' id='idField') | ||
div.form-group | ||
label(for='nameField') name | ||
input(type='text' name='name' id='nameField') | ||
input(type='submit') | ||
|
||
section | ||
h2 Response | ||
p.request | ||
span.request__method GET | ||
span | ||
spand.request__endpoint /users | ||
section.response | ||
code | ||
|
||
script. | ||
|
||
const apiId = '#{apiId}' | ||
const apiUrl = '#{apiUrl}/' | ||
const cognitoUserPoolClientId = '#{cognitoUserPoolClientId}' | ||
|
||
const queryStringParams = new URLSearchParams(window.location.search) | ||
const cognitoCode = queryStringParams.get('code') | ||
let cognitoIdentityToken = localStorage.getItem('cognitoIdentityToken') | ||
|
||
const form = document.querySelector('form') | ||
form.addEventListener('submit', onApiInvokeFormSubmit) | ||
|
||
fetch('users') | ||
.then(setResponseText) | ||
.catch(setResponseText) | ||
|
||
if (cognitoCode) { | ||
exchangeCodeForAccessToken() | ||
.then(response => response.json()) | ||
.then(json => { | ||
if (json.id_token) { | ||
cognitoIdentityToken = json.id_token | ||
localStorage.setItem('cognitoIdentityToken', cognitoIdentityToken) | ||
} | ||
}) | ||
} | ||
|
||
function convertJsonToFormUrlEncoded(json) { | ||
const oAuthTokenBodyArray = Object.entries(json).map(([key, value]) => { | ||
const encodedKey = encodeURIComponent(key) | ||
const encodedValue = encodeURIComponent(value) | ||
|
||
return `${encodedKey}=${encodedValue}` | ||
}) | ||
|
||
return oAuthTokenBodyArray.join('&') | ||
} | ||
|
||
function exchangeCodeForAccessToken() { | ||
const oauthTokenBodyJson = { | ||
grant_type: 'authorization_code', | ||
client_id: cognitoUserPoolClientId, | ||
code: cognitoCode, | ||
redirect_uri: apiUrl | ||
} | ||
const oauthTokenBody = convertJsonToFormUrlEncoded(oauthTokenBodyJson) | ||
|
||
return fetch(`https://${apiId}.auth.us-east-1.amazoncognito.com/oauth2/token`, | ||
{ | ||
method: 'POST', | ||
headers: { | ||
['Content-Type']: 'application/x-www-form-urlencoded' | ||
}, | ||
body: oauthTokenBody | ||
}) | ||
} | ||
|
||
function onApiInvokeFormSubmit (event) { | ||
event.preventDefault() | ||
const method = document.getElementById('methodField').value | ||
const id = document.getElementById('idField').value | ||
const name = document.getElementById('nameField').value | ||
const endpoint = id ? 'users/' + id : 'users' | ||
const body = ['POST', 'PUT'].includes(method) ? JSON.stringify({ name: name }) : undefined | ||
const headers = { | ||
'content-type': 'application/json', | ||
'Authorization': cognitoIdentityToken | ||
} | ||
|
||
document.querySelector('.request__method').innerText = method | ||
document.querySelector('.request__endpoint').innerText = `/${endpoint}` | ||
|
||
return fetch(endpoint, { | ||
method, | ||
headers, | ||
body | ||
}) | ||
.then(setResponseText) | ||
.catch(setResponseText) | ||
} | ||
|
||
function setResponseText(response) { | ||
const contentType = response.headers.get('content-type') | ||
if (contentType.includes('application/json')) { | ||
return response.json().then(json => { | ||
document.querySelector('code').innerText = JSON.stringify(json, null, 4) | ||
}) | ||
} | ||
|
||
return response.text().then(text => { | ||
document.querySelector('code').innerText = text | ||
}) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.