Containers
How to use Application Load Balancer and Amazon Cognito to authenticate users for your Kubernetes web apps
This post describes how to use Amazon Cognito to authenticate users for web apps running in an Amazon Elastic Kubernetes Services (Amazon EKS) cluster.
Behind any identity management system resides a complex network of systems meant to keep data and services secure. These systems handle functions such as directory services, access management, identity authentication, and compliance auditing. Building such a system from scratch requires specialized expertise that many dev teams lack. Customers have told us that instead of implementing the same authentication code across multiple applications, they prefer using a service that manages and secures user identity for them.
Services like Amazon Cognito allow customers to add authentication, authorization, and user management to web and mobile apps without creating and managing an identity management system. Using Amazon Cognito, you can enable your users to sign in directly with a user name and password or through a third party such as Facebook, Amazon, Google, or Apple.
Amazon EKS customers that use Application Load Balancer (ALB) can use Amazon Cognito to handle user registration and authentication without writing code to handle routine tasks such as user sign-up, sign-in, sign-out, and so on. This is because Application Load Balancer has built-in support for user authentication. In addition to Amazon Cognito, ALB natively integrates with any OpenID Connect protocol compliant identity provider (IdP), providing secure authentication and a single sign-on experience across your applications.
Authentication using Application Load Balancer
The architecture presented in this post authenticates users as they access the sample application. The application is exposed publicly using an Application Load Balancer. ALB securely authenticates each request when users visit the app and selectively forwards authenticated requests to the app.
ALB checks users’ request headers for an AWSELB
authentication session cookie. For an unauthenticated session, the cookie is absent. Therefore, ALB redirects unauthenticated users to a login page, which is hosted by Amazon Cognito hosted UI. Amazon Cognito hosted UI authenticates or registers the user. When users successfully sign in, Amazon Cognito redirects them back to the ALB with an authorization grant code.
The ALB presents the authorization grant code back to Amazon Cognito’s token endpoint and receives ID and access tokens. Next, the ALB exchanges the access token with Amazon Cognito user info endpoint for user claims, which contain user details such as the user’s email, phone number, and so on. Then the ALB redirects the user back to the original URI, this time setting the AWSELB
authentication session cookie.
When ALB receives a request with AWSELB
cookie, it validates it and forwards the user info to backends with the X-AMZN-OIDC-* HTTP headers set. The application decodes these headers to get user information.
The application doesn’t handle user authentication, registration, or managing session timeout in this scenario. We don’t even create a login page. We offload that responsibility to Amazon Cognito hosted UI.
Solution
We will use a sample application called Yelb for this demo. Yelb’s user interface will be exposed to users on the internet using an ALB. For DNS, we will use Amazon Route 53, and we’ll get a TLS certificate from AWS Certificate Manager (ACM).
You will need a registered domain name to follow along. You can use any registered domain, even one that you may not have registered using Route 53.
Prerequisites
You will need the following to complete the tutorial:
Note: We have tested the CLI steps in this post on Amazon Linux 2.
Let’s start by setting a few environment variables:
Create an EKS cluster using eksctl
:
You can proceed to the next steps while you wait for cluster creation.
Configure name resolution
When users visit the sample app at https://sample.{your-domain}.com
, Route 53 will route traffic to the ALB that exposes the user interface. Let’s create a Route 53 hosted zone and a record named sample.{your-domain}.com
:
Get a public certificate
Users will access our application securely at https://sample.{your-domain}.com, so we also need a TLS certificate. AWS Certificate Manager can create a TLS certificate that we can use with ALB.
Request an ACM certificate:
Before the Amazon certificate authority (CA) can issue a certificate for your site, ACM must prove that you own or control the domain. We will use DNS validation.
Create a record in the Route53 hosted zone so ACM can validate domain ownership:
Wait for two minutes and verify that the certificate has been issued:
The output should be ISSUED
. We recommend using watch
to run the above command until the certificate is issued.
Create a user directory
As users register to access our sample application, their information is securely stored in the Amazon Cognito user pool. A user pool is a user directory in Amazon Cognito. In addition to providing user directory management and user profiles, user pools also provide a built-in, customizable web UI called Amazon Cognito hosted UI to sign in and register users. We will use this web UI to sign in and register users.
Create an Amazon Cognito user pool:
Create a user pool app client:
Configure the app client:
You can find detailed information about app client settings terminology in Amazon Cognito documentation.
Amazon Cognito allows you to use your own domain for the web UI used to sign in and sign up users. Alternatively, you can use an Amazon Cognito domain (amazoncognito.com
) and customize the domain prefix. When you use an Amazon Cognito domain, the domain for your app is https://<domain_prefix>.auth.<region>.amazoncognito.com
.
We will use an Amazon Cognito domain for this demo. Create a domain using the hosted Amazon Cognito domain:
Install AWS Load Balancer Controller
Let’s return to the EKS cluster. The first thing we need to install in the EKS cluster is the AWS Load Balancer Controller, which is a controller that manages AWS Elastic Load Balancers for a Kubernetes cluster.
Run the following commands to install the AWS Load Balancer Controller into your cluster:
Deploy ExternalDNS
Next, we’ll deploy ExternalDNS, which is an open-source Kubernetes controller that synchronizes exposed Kubernetes services and ingresses with DNS providers like Route 53.
ExternalDNS will automatically create an alias record in Route 53 that will route users to the application’s ALB. You also have the option of not using ExternalDNS and managing Route 53 or your DNS provider directly.
The steps in this post assume that you will use ExternalDNS along with Route 53 for DNS management.
Create an IAM policy that will allow the ExternalDNS controller pod to update the Route53 record:
In production environments, consider scoping the policy to only permit updates to explicitly mentioned hosted zone IDs.
Create a service account and associate it with the IAM policy we just created:
eksctl
will also create a namespace called sample.
Install ExternalDNS:
You can verify that ExternalDNS deployed successfully by checking logs:
The output should look something like this:
Deploy the demo application
Now that we have configured the EKS cluster, Amazon Cognito, and Route53, and we have a TLS certificate from ACM, it’s time to deploy the demo application.
Create an Amazon ECR repository where we will store the container image for the demo application:
Clone the sample code repository and then build, push, and deploy the application:
Create a Kubernetes ingress to expose the service:
Notice the annotations in the ingress manifest.
alb.ingress.kubernetes.io/listen-ports
: configures the ALB with HTTP and HTTPS listener portsalb.ingress.kubernetes.io/certificate-arn
: Configures the secure listener with an ACM provided certificatealb.ingress.kubernetes.io/auth*
: enable authentication in ALB using Amazon Cognitoalb.ingress.kubernetes.io/auth-idp-cognito
: provides the UserPool’s Arn, UserPoolClientId, and UserPoolDomainalb.ingress.kubernetes.io/auth-on-unauthenticated-request
: configures ALB to authenticate unauthenticated requests
With this step, we have completed the deployment. Let’s test the setup and verify that the sample application is only available to authenticated users.
Test authentication
Point your browsers to the URL of the sample app in your cluster. You can get the URL using the previously configured environment variable.
Your browser will be redirected to a sign-in page. This page is provided by Amazon Cognito hosted UI.
Since this is your first time accessing the application, sign up as a new user. The data you input here will be saved in the Amazon Cognito user pool you created earlier in the post. For security reasons, we recommend using a disposable email address.
Once you sign in, ALB will send you to the sample app’s UI:
The application gets the user’s identity by parsing request headers.
Extracting an authenticated user’s identity
You may be asking, “How does the application identify the user?”
After ALB authenticates a user successfully, it sends the user claims received from Amazon Cognito to the application. The ALB signs the user claim so that applications can verify the signature and verify that the load balancer sent the claims.
The load balancer adds the following HTTP headers:
x-amzn-oidc-accesstoken
: The access token from the token endpoint, in plain text.
x-amzn-oidc-identity
: The subject field (sub
) from the user info endpoint, in plain text.
x-amzn-oidc-data
: The user claims, in JSON web tokens (JWT) format.
The target application gets the user identity by decoding x-amzn-oidc-data
HTTP headers as shown in sample-ui-code/app.py:
Cleanup
Use the commands below to delete resources created during this post:
Conclusion
This post demonstrates how you can use ALB’s built-in authentication to authenticate users without writing authentication code in your application. Your Kubernetes web apps that use an ALB as Kubernetes Ingress can authenticate users by using Amazon Cognito user pool as an identity provider. Additionally, Amazon Cognito hosted UI provides you an OAuth2.0 compliant authorization server that provides default implementation of end user flows such as registration, authentication, and so on.
With this architecture, you don’t have to code authentication flows such as user sign-in or sign-up in your applications. ALB handles user registration and authentication and passes the user’s identity to your applications.