Skip to content

Commit

Permalink
Support for Nextstrain CLI's new means of authentication with IdPs
Browse files Browse the repository at this point in the history
Nextstrain CLI will start using OIDC/OAuth2's authorization code flow to
interact with not just AWS Cognito but other IdPs as well (i.e. as used
in other deployments of nextstrain.org).

To support this without hardcoding or onerous user-side configuration,
Nextstrain CLI will start using the standard OIDC configuration
endpoint, /.well-known/openid-configuration, to auto-discover necessary
configuration about both the IdP to talk to and the client it should be.

Related-to: <nextstrain/private#94>
  • Loading branch information
tsibley committed Nov 20, 2023
1 parent 24d0a31 commit 17722df
Show file tree
Hide file tree
Showing 11 changed files with 312 additions and 7 deletions.
43 changes: 42 additions & 1 deletion aws/cognito/clients.tf
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,15 @@ resource "aws_cognito_user_pool_client" "nextstrain-cli" {

name = "nextstrain-cli"

# Allow Secure Remote Password (SRP) auth, plus refresh token auth (required).
# Allow client to use OAuth (with the authorization code grant type only)
# against the user pool, plus Secure Remote Password (SRP) auth and refresh
# token auth (required).
allowed_oauth_flows_user_pool_client = true
allowed_oauth_flows = ["code"]
allowed_oauth_scopes = ["email", "openid", "phone", "profile"]

supported_identity_providers = ["COGNITO"]

explicit_auth_flows = [
"ALLOW_USER_SRP_AUTH",
"ALLOW_REFRESH_TOKEN_AUTH",
Expand All @@ -96,6 +104,39 @@ resource "aws_cognito_user_pool_client" "nextstrain-cli" {
refresh_token = "days"
}

# Allowed redirection destinations to complete authentication.
#
# We'd prefer to use 127.0.0.1 instead of localhost to avoid name resolution
# issues on end user systems, issues which are known to occur. The "OAuth
# 2.0 for native apps" best current practice (RFC 8252) suggests as much¹, but
# alas, Cognito's https-requirement exception for localhost is not applied to
# 127.0.0.1.
#
# Similarly, we'd prefer to register without an explicit port and rely on the
# same RFC's stipulation of relaxed port matching for localhost², but alack,
# Cognito doesn't follow that either and requires strict port matching.
#
# Since the CLI may not always be able to listen on a specific port given
# other services that might be running, and there's also value in random
# choice making interception harder, register a slew of ports for use and let
# Nextstrain CLI draw from the list.
# -trs, 19 Nov 2023
#
# ¹ <https://datatracker.ietf.org/doc/html/rfc8252#section-8.3>
# ² <https://datatracker.ietf.org/doc/html/rfc8252#section-7.3>
callback_urls = formatlist("http://localhost:%d/", random_integer.nextstrain_cli_callback_port[*].result)

read_attributes = local.user_attributes
write_attributes = setsubtract(local.user_attributes, ["email_verified", "phone_number_verified"])
}

resource "random_integer" "nextstrain_cli_callback_port" {
# AWS Cognito supports 100 callback URLs per client
# <https://docs.aws.amazon.com/cognito/latest/developerguide/limits.html#resource-quotas>
count = 99

# IANA-defined port range for dynamic use.
# <https://datatracker.ietf.org/doc/html/rfc6335#section-6>
min = 49152
max = 65535
}
4 changes: 4 additions & 0 deletions aws/cognito/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ output "OAUTH2_CLI_CLIENT_ID" {
value = aws_cognito_user_pool_client.nextstrain-cli.id
}

output "OAUTH2_CLI_CLIENT_REDIRECT_URIS" {
value = aws_cognito_user_pool_client.nextstrain-cli.callback_urls
}

output "OAUTH2_LOGOUT_URL" {
value = format("https://%s/logout", coalesce(
one(aws_cognito_user_pool_domain.custom[*].domain),
Expand Down
34 changes: 28 additions & 6 deletions docs/production.rst
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ file are::
OAUTH2_CLIENT_ID
OAUTH2_CLIENT_SECRET
OAUTH2_CLI_CLIENT_ID
OAUTH2_CLI_CLIENT_REDIRECT_URIS
OIDC_USERNAME_CLAIM
OIDC_GROUPS_CLAIM

Expand Down Expand Up @@ -282,13 +283,34 @@ CLI client
A `public, native application client <oauth2-clients_>`__ is required for use
by the :doc:`Nextstrain CLI <cli:index>` and is permitted by the app server to
make `Bearer`-authenticated requests. Its id is configured by
`OAUTH2_CLI_CLIENT_ID`.
`OAUTH2_CLI_CLIENT_ID`. The client registration must allow:

.. note::
Currently Nextstrain CLI is tightly bound to AWS Cognito and requires
its Secure Remote Password authentication flow implemented outside of
the standard OAuth 2.0 flows. We anticipate changing this in the
future.
- the authorization code flow, ideally with PKCE_ support

- issuance of refresh tokens, either by default or by requesting the
`offline_access` scope

- at least one authentication redirection (sometimes "callback") URL of
`http://127.0.0.1:<port>/` or `http://localhost:<port>/`

The CLI auto-discovers its OpenID client configuration (and the IdP
configuration) from the app server. The app server must be configured to know
the CLI client's redirect URIs with `OAUTH2_CLI_CLIENT_REDIRECT_URIS` so the
URLs can be included in the discovery response.

If the IdP allows for `http://` redirect URIs for loopback IPs (e.g.
`127.0.0.1`), then the loopback IP should be preferred over using `localhost`,
as per best current practice described in `RFC 8252 § 8.3`_.

If the IdP allows relaxed port matching for loopback IP/localhost redirect
URIs, as per best current practice described in `RFC 8252 § 7.3`_, then only a
single redirect URI needs to be registered with the IdP. Otherwise, multiple
redirect URIs with varying ports should be registered to allow the CLI
alternatives to choose from in case it can't bind a given port on a user's
computer.

.. _RFC 8252 § 7.3: https://datatracker.ietf.org/doc/html/rfc8252#section-7.3
.. _RFC 8252 § 8.3: https://datatracker.ietf.org/doc/html/rfc8252#section-8.3


Token lifetimes
Expand Down
4 changes: 4 additions & 0 deletions env/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ output "OAUTH2_CLI_CLIENT_ID" {
value = module.cognito.OAUTH2_CLI_CLIENT_ID
}

output "OAUTH2_CLI_CLIENT_REDIRECT_URIS" {
value = module.cognito.OAUTH2_CLI_CLIENT_REDIRECT_URIS
}

output "OAUTH2_LOGOUT_URL" {
value = module.cognito.OAUTH2_LOGOUT_URL
}
Expand Down
1 change: 1 addition & 0 deletions env/production/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"OIDC_IDP_URL": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_Cg5rcTged",
"OAUTH2_CLIENT_ID": "rki99ml8g2jb9sm1qcq9oi5n",
"OAUTH2_CLI_CLIENT_ID": "2vmc93kj4fiul8uv40uqge93m5",
"OAUTH2_CLI_CLIENT_REDIRECT_URIS": [],
"OAUTH2_LOGOUT_URL": "https://login.nextstrain.org/logout",
"COGNITO_USER_POOL_ID": "us-east-1_Cg5rcTged",
"OIDC_USERNAME_CLAIM": "cognito:username",
Expand Down
19 changes: 19 additions & 0 deletions env/testing/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

100 changes: 100 additions & 0 deletions env/testing/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,106 @@
"OIDC_IDP_URL": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_zqpCrjM7I",
"OAUTH2_CLIENT_ID": "6qiojrhr8tibt0f6hphnm1osp1",
"OAUTH2_CLI_CLIENT_ID": "9opa27o74f4jsq8g4a34e1mqr",
"OAUTH2_CLI_CLIENT_REDIRECT_URIS": [
"http://localhost:49161/",
"http://localhost:49334/",
"http://localhost:49359/",
"http://localhost:49398/",
"http://localhost:49603/",
"http://localhost:50044/",
"http://localhost:50110/",
"http://localhost:50132/",
"http://localhost:50467/",
"http://localhost:50667/",
"http://localhost:50712/",
"http://localhost:51264/",
"http://localhost:51333/",
"http://localhost:51413/",
"http://localhost:51467/",
"http://localhost:51596/",
"http://localhost:51664/",
"http://localhost:51953/",
"http://localhost:51974/",
"http://localhost:51977/",
"http://localhost:52272/",
"http://localhost:52342/",
"http://localhost:52361/",
"http://localhost:52564/",
"http://localhost:52621/",
"http://localhost:52673/",
"http://localhost:53216/",
"http://localhost:53267/",
"http://localhost:53375/",
"http://localhost:53624/",
"http://localhost:53644/",
"http://localhost:54071/",
"http://localhost:55078/",
"http://localhost:55286/",
"http://localhost:55296/",
"http://localhost:55357/",
"http://localhost:55419/",
"http://localhost:55462/",
"http://localhost:55724/",
"http://localhost:56972/",
"http://localhost:57135/",
"http://localhost:57194/",
"http://localhost:57255/",
"http://localhost:57321/",
"http://localhost:57411/",
"http://localhost:57564/",
"http://localhost:57591/",
"http://localhost:57649/",
"http://localhost:57654/",
"http://localhost:57883/",
"http://localhost:58259/",
"http://localhost:58549/",
"http://localhost:58826/",
"http://localhost:59071/",
"http://localhost:59359/",
"http://localhost:59688/",
"http://localhost:60055/",
"http://localhost:60107/",
"http://localhost:60205/",
"http://localhost:60590/",
"http://localhost:60790/",
"http://localhost:60883/",
"http://localhost:60897/",
"http://localhost:60911/",
"http://localhost:61155/",
"http://localhost:61325/",
"http://localhost:61369/",
"http://localhost:61400/",
"http://localhost:61406/",
"http://localhost:61553/",
"http://localhost:62190/",
"http://localhost:62405/",
"http://localhost:62439/",
"http://localhost:62467/",
"http://localhost:62638/",
"http://localhost:62726/",
"http://localhost:63016/",
"http://localhost:63103/",
"http://localhost:63309/",
"http://localhost:63318/",
"http://localhost:63387/",
"http://localhost:63480/",
"http://localhost:63526/",
"http://localhost:63704/",
"http://localhost:63743/",
"http://localhost:63775/",
"http://localhost:64008/",
"http://localhost:64373/",
"http://localhost:64410/",
"http://localhost:64446/",
"http://localhost:64520/",
"http://localhost:64694/",
"http://localhost:64960/",
"http://localhost:64972/",
"http://localhost:65032/",
"http://localhost:65086/",
"http://localhost:65113/",
"http://localhost:65121/"
],
"OAUTH2_LOGOUT_URL": "https://nextstrain-testing.auth.us-east-1.amazoncognito.com/logout",
"COGNITO_USER_POOL_ID": "us-east-1_zqpCrjM7I",
"OIDC_USERNAME_CLAIM": "cognito:username",
Expand Down
9 changes: 9 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,15 @@ app.route("/schemas/*")
.all((req, res, next) => next(new NotFound()));


/* OpenID Connect 1.0 configuration. Retrieved by Nextstrain CLI to
* discovery necessary authentication details.
*
* <https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata>
*/
app.routeAsync("/.well-known/openid-configuration")
.getAsync(endpoints.openid.providerConfiguration);


/* Auspice HTML pages and assets.
*
* Auspice hardcodes URL paths that start with /dist/… in its Webpack config,
Expand Down
35 changes: 35 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,24 @@ export const OAUTH2_LOGOUT_URL = fromEnvOrConfig("OAUTH2_LOGOUT_URL", OIDC_CONFI
export const OAUTH2_SCOPES_SUPPORTED = new Set(fromEnvOrConfig("OAUTH2_SCOPES_SUPPORTED", OIDC_CONFIGURATION.scopes_supported));


/**
* Effective OpenID Connect (OIDC) identity provider configuration document
* after potential local overrides.
*
* Defined here to keep the overridden fields close to their declarations
* above.
*/
export const EFFECTIVE_OIDC_CONFIGURATION = {
...OIDC_CONFIGURATION,
issuer: OIDC_ISSUER_URL,
jwks_uri: OIDC_JWKS_URL,
authorization_endpoint: OAUTH2_AUTHORIZATION_URL,
token_endpoint: OAUTH2_TOKEN_URL,
end_session_endpoint: OAUTH2_LOGOUT_URL,
scopes_supported: OAUTH2_SCOPES_SUPPORTED,
};


/**
* OAuth2 client id of nextstrain.org server as registered with our IdP (e.g.
* our Cognito user pool).
Expand Down Expand Up @@ -279,6 +297,23 @@ export const OAUTH2_CLIENT_SECRET = fromEnvOrConfig("OAUTH2_CLIENT_SECRET", null
export const OAUTH2_CLI_CLIENT_ID = fromEnvOrConfig("OAUTH2_CLI_CLIENT_ID");


/**
* OAuth2 client redirect URIs (e.g. callback URLs) for Nextstrain CLI as
* registered with the IdP.
*
* These URLs are not themselves used by the server but are provided to
* (discovered by) Nextstrain CLI in a client configuration section of the
* OpenID configuration document served at /.well-known/openid-configuration.
*
* The name of this config var uses "redirect_uri" as the term since that's the
* literal field name used by the OIDC/OAuth2 specs in several places (initial
* auth requests, client metadata registration/querying, etc.).
*
* @type {string[]}
*/
export const OAUTH2_CLI_CLIENT_REDIRECT_URIS = fromEnvOrConfig("OAUTH2_CLI_CLIENT_REDIRECT_URIS");


/**
* ID token claim field containing the username for a user.
*
Expand Down
2 changes: 2 additions & 0 deletions src/endpoints/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as charon from './charon/index.js';
import * as cli from './cli.js';
import * as groups from "./groups.js";
import * as openid from './openid.js';
import * as options from './options.js';
import * as sources from './sources.js';
import * as static_ from './static.js';
Expand All @@ -10,6 +11,7 @@ export {
charon,
cli,
groups,
openid,
options,
sources,
static_ as static,
Expand Down
Loading

0 comments on commit 17722df

Please sign in to comment.