Skip to content

Commit

Permalink
Update documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
instantiator committed Mar 16, 2023
1 parent 92cd91b commit d704c61
Showing 1 changed file with 48 additions and 27 deletions.
75 changes: 48 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,6 @@ A simple template to develop, publish, and test a simple .NET service backend on

_Hopefully this will accelerate some early prototyping for some of my projects, and perhaps you'll find it useful as a reference too._

### Roadmap

- [x] Update deployment scripts to set the test user password directly
- [x] Add tests, and testing scripts for named stacks and transient stacks
- [x] Apply OAuth scopes to API endpoints in `template.yaml`
- [ ] Add CORS headers to 4XX and 5XX responses in `template.yaml`
- [ ] Create an `OPTIONS` lambda handler to provide CORS headers
- [ ] Provide CORS headers in all other handler responses, too

## Design

The rocket ship design...
Expand All @@ -22,8 +13,10 @@ The rocket ship design...
Included in this template:

- [x] SAM template
- [x] Sample function (Lambda)
- [x] API Gateway
- [x] Sample functions (.NET 6 Lambdas)
- [x] Endpoint tests (MSTest)
- [x] API Gateway configuration
- [x] CORS header support
- [x] Database (DynamoDb)
- [x] Auth (Cognito)

Expand Down Expand Up @@ -88,19 +81,21 @@ brew install --cask dotnet-sdk

## Deployment scripts

* `deploy-stack-aws.sh -a <profile-name> -s <stack-name>` - build and deploy to AWS
* `deploy-stack-aws.sh -a <profile-name> -s <stack-name> -u <test-user-email>` - build and deploy the application stack to AWS
* `delete-stack-aws.sh -a <profile-name> -s <stack-name>` - delete a given stack on AWS
* `test-stack.sh -a <profile-name> -s <stack-name>` - run the endpoint tests against a given stack on AWS
* `test-transient-stack.sh -a <profile-name> -u <test-user-email>` - deploy a stack, run the endpoint tests against it, then delete it

In each case, you must provide a profile and stack.
In nearly all cases, you must provide an AWS profile available on your machine, and a stack name. The transient stack test script doesn't require a stack name - it makes one up for you. You must provide a test user email address.

## Auth

On first deployment, you new test user will receive a temporary password by email. This will then be immediately updated to `testpass1`. (This is hard coded in: `deploy-stack-aws.sh`.)
On first deployment, your new test user will receive a temporary password by email. This will then be immediately updated to `testpass1`. (This is hard coded in: `deploy-stack-aws.sh`.)

You should obtain and use the user's `AccessToken` as the `Authorization: Bearer` heading in calls to the API, eg.

```shell
curl -H "Authorization: Bearer <IdToken>" https://endpoint.uri.etc.
curl -H "Authorization: Bearer <AccessToken>" https://endpoint.uri.etc.
```

For all subsequent calls where you need a token, you can use the output from `auth-scripts/get-user-access-token.sh`, eg.
Expand All @@ -114,7 +109,7 @@ curl -H "Authorization: Bearer $ACCESS_TOKEN" https://endpoint.uri.etc.

The `test-stack.sh` and `test-transient-stack.sh` scripts will run the endpoint tests from: `src/SampleFunctions/EndpointTests`

These will exercise Cognito (retrieving user credentials through the `aws.cognito.signin.user.admin` OAuth scope), and then provide the token they received as in the `Authorization: Bearer <token>` header to interact with the APIs.
These will exercise Cognito (retrieving user credentials through the `aws.cognito.signin.user.admin` OAuth scope), and then provide the _access_ token they received as in the `Authorization: Bearer <token>` header to interact with the APIs.

## CORS

Expand All @@ -123,25 +118,29 @@ To access the APIs from a browser, additional CORS headers are required.
* Cross-origin requests are rejected by browsers because CORS headers aren't available.
* CORS headers are also required for 4XX errors from the Cognito authorizer, and 5XX from lambda execution.

CORS headers can be generated by the lambda methods themselves, and by Gateway for the 4XX and 5XX errors. An additional endpoint is required for the `OPTIONS` HTTP method which should also provide the CORS headers.
CORS headers can be generated by the lambda methods themselves, and by Gateway for the 4XX and 5XX errors.

The code in this template assume that the source of requests comes from `https://localhost:5001` (Coincidentally, 5001 is the port that Blazor web assembly apps are hosted on by default when running on your local machine.) If you build your own browser-based client, you'll need to update this everywhere it appears.
Browsers make a pre-flight check using the `OPTIONS` method before calling the actual method (eg. `GET` / `POST` etc.) for an API call. An additional endpoint is required for the `OPTIONS` HTTP method which should also provide the CORS headers. This is illustrated in `template.yaml` and the function code provided.

The code in this template assume that the source of requests comes from `https://localhost:5001` (Coincidentally, `5001` is the port that Blazor web assembly apps are hosted on your local machine by default.)

If you use a browser-based client, pay careful attention to the CORS configuration. You'll need to alter the `Access-Control-Allow-Origin` header found in each function and `template.yaml` to match the URL of the page that makes the call.

## Exercising the functions

To illustrate the flexibility, `FunctionOne` actually contains two handlers, and the template deploys two functions - one for each.
The template deploys two functions - one, called the root handler, is responsible for calls to `/FunctionOne`. The other, called the notes handler, is responsible for calls to `/FunctionOne/notes`.

For convenience of exploration, each deployed function is also mapped to two endpoints (one with auth, one without). When developing your application, give some though to which endpoints need authentication and which don't.
When developing your application, give some though to which endpoints need authentication and which don't. `template.yaml` applies an authorizer to the notes endpoint, but not to the root endpoint or to the `OPTIONS` events.

| Handler | Endpoint | Method | Auth | Notes |
|-|-|-|-|
| `FunctionOne.HandlerRoot:Handle` | /FunctionOne/ | GET | Not required. | Returns "ok". |
| `FunctionOne.HandlerRoot:Handle` | /FunctionOne/ | OPTIONS | Not required. | Supports a CORS pre-flight request for headers. |
| `FunctionOne.HandlerNotes:Handle` | /FunctionOne/notes/ | GET | Access token required. | Returns a count of the number of notes in the database table. |
| `FunctionOne.HandlerNotes:Handle` | /FunctionOne/notes/ | POST | Access token required. | Adds a new note to the database table. |
| `FunctionOne.HandlerRoot:Handle` | /FunctionOne/notes/ | OPTIONS | Not required. | Supports a CORS pre-flight request for headers. |
| `FunctionOne.HandlerRoot:Handle` | `/FunctionOne` | `GET` | Not required. | Returns "ok". |
| `FunctionOne.HandlerRoot:Handle` | `/FunctionOne` | `OPTIONS` | Not required. | Supports a CORS pre-flight request for headers. |
| `FunctionOne.HandlerNotes:Handle` | `/FunctionOne/notes` | `GET` | Access token required. | Returns a count of the number of notes in the database table. |
| `FunctionOne.HandlerNotes:Handle` | `/FunctionOne/notes` | `POST` | Access token required. | Adds a new note to the database table. |
| `FunctionOne.HandlerRoot:Handle` | `/FunctionOne/notes` | `OPTIONS` | Not required. | Supports a CORS pre-flight request for headers. |

### Quick test
### Manual test

Retrieve an access token for this user:

Expand Down Expand Up @@ -169,10 +168,18 @@ Finished? Tidy up so that resources don't accidentally incur costs. Particularly
./delete-stack-aws.sh -a <aws-profile> -s <stack-name>
```

You could also delete the stack from the CloudFormation AWS console.
**NB. There is a known issue that prevents stack deletion from completing. You can complete deletion in the AWS console.**

## Misc notes

### API gateway / Cognito user pool authorizers vs. tokens

If scopes are not applied to an API Gateway function's event source, the authorizer will accept the user's _id token_ by default.

If scopes are applied, then the authorizer will accept the user's _access token_ instead.

NB. scopes _are_ applied in `template.yaml` - which is why these functions require the _access token_.

### OS X aws-vault keychain access

To manage the keychain for aws-vault, you'll want to import it into the Keychain Access application:
Expand All @@ -187,6 +194,7 @@ Client apps are assumed to be simple (perhaps a [SPA](https://en.wikipedia.org/w

I've used Blazor webasm in the past to build a simple responsive app quickly, and can recommend it - but you may already have a solution (perhaps a trendy javascript or TS framework) in mind.

Where using a browser-based client application, pay careful attention to the CORS configuration. You'll need to alter the `Access-Control-Allow-Origin` header found in each function and `template.yaml` to match the URL of the page that makes the call.

### LocalStack

Expand All @@ -200,3 +208,16 @@ With thanks to...

* [Nathaniel Beckstead](https://github.com/scriptingislife), for: [How to add Cognito to your AWS SAM app](https://scriptingis.life/Cognito-AWS-SAM)
* Jeff Adams, [ten mile square](https://tenmilesquare.com/), for: [AWS SAM API with Cognito](https://tenmilesquare.com/resources/cloud-infrastructure/aws-sam-api-with-cognito/)

## Changelog

### 2023-03-16

Add testing support, CORS, and simplify the provided lambda functions:

- [x] Update deployment scripts to set the test user password directly
- [x] Add tests, and testing scripts for named stacks and transient stacks
- [x] Apply OAuth scopes to API endpoints in `template.yaml`
- [x] Add CORS headers to 4XX and 5XX responses in `template.yaml`
- [x] Create an `OPTIONS` lambda handler to provide CORS headers
- [x] Provide CORS headers in all other handler responses, too

0 comments on commit d704c61

Please sign in to comment.