How to configure Auth0 with AWS AppSync

Use any user directory that supports OpenID Connect with AppSync

Author's image
Tamás Sallai
4 mins

OpenID Connect with AppSync

AppSync supports multiple types of authorization providers, and one of them is OpenID Connect. It is an open standard and many user directories support it. Using that, you can integrate third-party login providers with an AppSync API.

How does it work?

Users are in the third-party system that handles authentication (logging in) and issues authentication tokens. Then when the user makes a request to AppSync they include this token. AppSync verifies that it is not forged and it indeed came from the OpenID Connect provider. The token contains information about the user, most importantly its identifier that resolvers in AppSync can use. As a result, what you can do with Cognito you can also configure for many other providers.

In this article we'll look into how to integrate Auth0 with AppSync. We'll have users in an Auth0 tenant, and call an AppSync API with tokens issued by that tenant. Then we'll also see what AppSync resolvers see when a client uses a third-party token.

Auth0 config

The first step is to configure an application on Auth0. This is where users can log in:

Auth0 application config

OAuth supports several ways to implement the login flow, but the most useful is based on a redirect to the auth endpoint and then a code parameter that is then exchanged for the token. This is roughly the same as for Cognito, described in this article.

This needs a callback URL, then I needed to disable the token endpoint authentication as the SPA has no access to the client secret:

Token endpoint authentication and the allowed callback URL

Then the last thing is to have some users:

Users in Auth0

Terraform

Since Auth0 supports Terraform, it is easy to wire everything together with code:

resource "auth0_client" "appsync" {
	name            = "Appsync-test-${random_id.id.hex}"
	app_type        = "spa"
	callbacks       = ["https://${aws_cloudfront_distribution.distribution.domain_name}"]
	oidc_conformant = true
	token_endpoint_auth_method = "none"

	jwt_configuration {
		alg = "RS256"
	}
}

resource "auth0_user" "user1" {
  connection_name = "Username-Password-Authentication"
  email = "user1@example.com"
  email_verified = true
  password = "Password.1"
}

AppSync

Now that there is a configured user directory with some users, the next thing is to configure the API. This part is rather simple, as AppSync only needs the domain:

AppSync configuration

While the client ID is optional, you'll need that on the client-side as it is a required parameter for most calls.

Why a domain is enough

AppSync validates the token, but why it only needs the auth domain? Validation needs a public key to check the signature, but the configuration does not need it. Where does is come from?

OpenID Connect specifies a location with all the necessary information. This is at https://<domain>/.well-known/openid-configuration and it is a JSON containing several fields:

{
	"issuer": "https://dev-at4in79i.us.auth0.com/",
	"authorization_endpoint": "https://dev-at4in79i.us.auth0.com/authorize",
	"token_endpoint": "https://dev-at4in79i.us.auth0.com/oauth/token",
	"userinfo_endpoint": "https://dev-at4in79i.us.auth0.com/userinfo",
	"jwks_uri": "https://dev-at4in79i.us.auth0.com/.well-known/jwks.json",
	...,
}

Notice the jwks_uri, which is a link to another JSON:

{
	"keys": [
		{
			"alg": "RS256",
			"kty": "RSA",
			"use": "sig",
			"n": "rgxLjmjj_...",
			"e": "AQAB",
			"kid": "eUhhBOwRy_...",
			"x5t": "SEmMAyXE3H...",
			"x5c": [
				"MIIDDTCCAfWgAwIBA..."
			]
		},
		...
	]
}

This contains a list of keys, identified by the kid (key ID). The token also has a kid. When AppSync validates the token it fetches the JWKs, find the matching key, then verify the signature using the parameters.

AWS documentation has an article that describes it for Cognito, but the idea is the same for other providers.

Client-side

The client-side wires the two services together. The browser redirects to Auth0 for login, where the login flow kicks in. This is where a username, a password, MFA, and any other information is taken from the user that is needed for logging in.

When Auth0 is happy with the answers, it redirects to the SPA with a code parameter. Finally, the client exchanges this code for an id_token:

OAuth flow

The exact flow is a bit more complicated as there are several parameters required on every step for it to work. Check out this article for a more in-depth analysis.

The id_token is what AppSync needs in the authorization header:

id_token in the authorization header

Under the hood, this is where AppSync does the token validation using the JWKs, and then it calls the resolvers.

Resolver-side

The $context.identity provides information about the user:

{
	"claims": {
		"sub": "auth0|62134bfe5ac54f0069f299c7",
		"aud": "UinUoTppV0jjuSbGJlkcC9UMH9DIH8SV",
		"iss": "https://dev-at4in79i.us.auth0.com/",
		"exp": 1645470280,
		"iat": 1645434280
	},
	"issuer": "https://dev-at4in79i.us.auth0.com/",
	"sub": "auth0|62134bfe5ac54f0069f299c7"
}

The sub is the ID of the user in Auth0. Usually, the API can use this to store and retrieve data for this user.

Conclusion

OpenID Connect provides a standards-based way to wire a third-party authentication provider with AppSync. With a few lines of configuration, you can use any compliant user directory with an API.

March 22, 2022
In this article