Skip to content

Commit

Permalink
Add documentation for odds, ends, and gotchas
Browse files Browse the repository at this point in the history
  • Loading branch information
casewalker committed Dec 12, 2023
1 parent f2babe2 commit 5d7332e
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 5 deletions.
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ This site relies on some environment variables:
- `NEXT_PUBLIC_COGNITO_HOSTED_UI_URL`

The base URL for the Hosted UI Cognito sign-in page associated with this
UserPool's application (the current working UserPool is "_Cognito for Connect
Call Centers_" (ID: _us-east-1_AZyvZQdFN_) and the "App integration" client is
"_Amplify App_")
User Pool's application (the current working User Pool is "_Cognito for
Connect Call Centers_" (ID: _us-east-1_AZyvZQdFN_) and the "App integration"
client is "_Amplify App_")

- `NEXT_PUBLIC_COGNITO_CLIENT_ID`

Expand All @@ -34,8 +34,9 @@ This site relies on some environment variables:
The URL of this web app

**Note:** This must be configured here as well as inside Cognito, in the
UserPool, in the App client, under "_Hosted UI_", under "_Allowed callback
URLs_"
User Pool, in the App client, under "_Hosted UI_", under "_Allowed callback
URLs_", and it should also be reflected in the Invitation Message under
"_Messaging_"

- `NEXT_PUBLIC_SSO_DEFAULT_DURATION`

Expand All @@ -51,3 +52,6 @@ This site relies on some environment variables:

This is a [Next.js](https://nextjs.org/) project bootstrapped with
[`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

See [The Inside Story](docs/THE_INSIDE_STORY.md) for more information about all
the random hurdles and gotchas that were overcome in order to get this working.
186 changes: 186 additions & 0 deletions docs/THE_INSIDE_STORY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# The Inside Story

This code functions to bring together AWS Cognito with AWS Connect using the
Lambda defined in the
[Custom AWS IDP](https://github.com/newjersey/custom-aws-idp)
repo. It utilizes Cognito's
[Hosted UI](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-hosted-ui-user-sign-in.html)
so that the baseline login security is fully managed by AWS (with JWTs
containing login session metadata). In order to send invite-emails to new users
and manage password resetting, AWS Simple Email Service (SES) has been used to
register a verified domain and a verified email. The Cognito User Pool is also
configured to require MFA. One of those MFA methods is allowed to be SMS, and
setting up the phone number for those text messages is done in Amazon Simple
Notification Service (SNS) and Amazon Pinpoint / Pinpoint SMS.

To make this all work took some unintuitive steps and some long lead times, so
this document serves to help anyone in the future trying to replicate or emulate
some parts of this system as well as to update the right things if this system
changes.

## Generating (and handling) a valid SAML Response

As described in the README for Custom AWS IDP, there was a lot of difficulty
generating a valid, signed SAML Response which AWS could consume. As well,
getting it all pieced together correctly took learning new things.

It turns out that SAML-based SSO seems to lean heavily on browsers to do a lot
of correct handling and redirecting. This happens after the SAML Response is
POSTed to the Service Provider (SP, in our case AWS) using the
`application/x-www-form-urlencoded` content type. As well, in order for AWS to
correctly redirect the browser to the right AWS Connect instance after
successful authentication, the `RelayState` must be included in that POST body.
As can also be seen in the Custom AWS IDP repo, the `SAMLResponse` is
transmitted in base64 format.

In order to POST the SAML Response and Relay State to AWS without forcing the
user to manually submit a form, a
[Self Submitting Form](../components/SelfSubmittingSsoForm.tsx) is used (also
using hidden inputs so the user doesn't have to see anything confusing either).

ASIDE: It is not useful for this work now, but an earlier draft (without the
need for a frontend with a call center picker) relied on a Lambda response with
content type `text/html` and an embedded self-submitting form as the body, like
this:

```html
<body onload="document.forms[0].submit()">
<form ... >
<!-- hidden inputs... -->
</form>
<h1>Loading...</h1>
</body>
```

I thought that was cool enough to keep around even though we are not using it.

## Setting up emails

We wanted to set up Cognito's *Messaging* configuration so that users would be
able to get an invitation-email with their first (temporary) password and also
so that users could receive emails to help for forgotten-password resets. As it
turned out, this was much harder than it seemed, requiring enough understanding
of email security and the AWS Email Service (SES) to get to the point where
emails could at least reach their destination without being bounced or dropped,
even though under V1, emails still end up in state junk folders.

### DMARC = DKIM + SPF

Domain-based Message Authentication, Reporting and Conformance (DMARC) helps
protect an email domain against spoofing and phishing. It does this using two
mechanisms:
1. The Sender Policy Framework (SPF) which specifies what servers/domains are
authorized to be mail-senders for a given email domain, and
2. DomainKeys Identified Mail (DKIM) which adds digital signatures to all
outgoing mail, allowing receivers to verify the mail sender

The state uses DMARC (thankfully) with "relaxed alignment," which just means we
are allowed to set up the MAIL-FROM domain as a custom subdomain and still pass
DMARC. The fine details of these protocols are not important here, but with that
baseline information in place, this system required going to Amazon SES and
configuring:
1. *innovation.nj.gov* as a verified identity domain
1. Configuring the use of *Easy DKIM* under "Advanced DKIM settings" with
*RSA_2048_BIT* DKIM signing key length and DKIM signatures enabled
1. Requiring OIT to publish three CNAME records
2. Configuring the Custom MAIL FROM domain *aws-email.innovation.nj.gov*
1. Requiring OIT to publish AWS-provided DNS records
2. *[email protected]* as a verified identity email address
1. Requiring OIT/ops to create the email address and provide us with access
to its inbox; AWS sends a verification email to the inbox and the address
is only verified after a link in that verification email is clicked
3. Production Mode (by default, SES starts out in Sandbox Mode where emails can
only be sent to other SES verified email addresses, a request must be made to
graduate from Sandbox to Production, and I don't remember but this may also
require a request for some limit increase)

Once the setup is complete, Cognito can be configured to use the verified email
address as the "FROM Email Address" by editing "Email" on the Messaging tab in
the User Pool. An optional "FROM Sender Name" can also be configured, such as
"New Jersey Call Centers <[email protected]>".

Special thanks to the New Jersey Cyber Communication & Integration Cell (NJCCIC)
and AWS support engineers who helped us figure out how to set this up.

## Setting up SMS

Getting SMS configured required a number of steps, lead time, and also some
bug-dodging trickery.

### Allowing SMS for MFA without shooting yourself in the foot

As it turns out, at least as of this writing in early December 2023, if a User
Pool wants to allow both MFA methods (Authenticator apps + SMS message)
utilizing the Cognito Hosted UI, you __*should not*__ configure the "SMS
message" option while in the User Pool creation flow. If "SMS message" is
selected, then on the following creation flow page you will see that
`phone_number` is one of the user "Required Attributes". When the phone number
is a required attribute, it becomes impossible for the user to choose SMS as
their MFA method. Only when `phone_number` is not required does Cognito
correctly prompt the user to choose SMS or Authenticator App during their first
login. At least this is how it was for our system which does not allow
self-registration but does not necessarily know phone numbers when a user is
created.

This seems pretty obviously like a bug in Cognito, but while I did try to urge a
support engineer to submit a bug report for me when I encountered this issue, I
am not confident that one was opened.

### An originating phone number

Cognito links to
[this documentation](https://docs.aws.amazon.com/sns/latest/dg/channels-sms-originating-identities.html)
for creating an originating identity. We use a Toll-Free Origination Number.
Fortunately for me, I did not set this piece up, but then unfortunately for this
documentation, I don't have a summary of any hurdles that had to be overcome
here.

### Enabling SMS

Cognito links to
[this documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-sms-settings.html)
for setting up SMS for Cognito. In practice, it primarily involved following the
steps which Cognito suggests on the *Messaging* tab of the User Pool: moving SNS
out of Sandbox mode (just like SES) and requesting a limit increase (or a few
increases?) to SNS and possibly also Pinpoint and Pinpoint SMS.

Something that was a bit surprising was that even after following all the steps
and getting SMS correctly setup, there is still an *Info* box on the SMS part of
the Messaging page with advice for the steps necessary to set up SMS. For this
confusing UI, I believe an AWS support engineer did submit a request to change
it so the info box goes away when everything is correctly configured.

Where to configure the base URL

## Multiple truth sources

Unfortunately with the way the code ended up in two repos and with the different
AWS products, a few pieces of information must be manually duplicated in a few
places:
* The URL for this web app:
1. Must be configured in Amplify as the `NEXT_PUBLIC_COGNITO_REDIRECT_URI`
environment variable, as described in the README
2. Must be configured in Cognito in the App Client of the User Pool as an
*Allowed Callback URL*
3. Must be configured in Cognito under Messaging as the URL in the *Invitation
Message* template
* The User Pool ID and App Client ID:
1. These must be configured as environment variables in the Lambda's
serverless.yml file (the other repo)
2. The App Client ID must be configured in Amplify as the
`NEXT_PUBLIC_COGNITO_CLIENT_ID` environment variable, as described in the
README
3. Perhaps this should be removed, but they are sometimes referenced either by
name or by ID in READMEs in both repos, and this should stay up-to-date
with reality

## How to create a user?

To create a user who can receive a good temporary password in their invitation
email:
1. Go to the User Pool, under the "Users" tab press "*Create user*"
2. Choose "*Send an email invitation*"
3. Provide their email address
4. Set the email address as verified (otherwise the email won't be sent)
5. Select "*Generate a password*"
6. Press the button at the bottom "*Create user*"

0 comments on commit 5d7332e

Please sign in to comment.