Introduction
I do not want my app to perform OIDC, and I do not want unauthenticated traffic reaching my private subnets. I have a simple React SPA, and when keeping these prerequisites in mind would lead us to perform OIDC authentication using native AWS services. This can be at best done with the application load balancer (ALB). The ALB can be configured to perform OIDC authentication at the listener level. Sounds great, but like everything in life, there are some gotchas..
AWS ALB Entra ID OIDC Authentication
Let’s configure the HTTPS ALB listener to perform Entra ID OIDC authentication. As a prerequisite, we assume that you have already configured an Azure App Registration. This is a terraform snippet that shows the ALB listener configuration:
| |
That’s it! Now you can access your application, and all HTTPS requests will be redirected to the Entra ID login page.
After successful authentication, the user will be redirected back to the application as indicated in the target group
arn target_group_arn = aws_lb_target_group.backend.arn. The callback URLs or redirect URIs configured for the
application are: https://myapp.aws.cloud/oauth2/idpresponse.
All the above details for OIDC are available in the well-known endpoint that is a standardized JSON file containing the metadata such as the issuer, authorization endpoint, token endpoint, user info endpoint, etc. The well-known endpoint for Entra ID is available at the following URL:
https://login.microsoftonline.com/<Azure_AD_tenant_ID>/.well-known/openid-configuration
Where is my ID token?
If you have done Entra ID OIDC authentication before, you would be used to receiving the ID, access, and refresh tokens
back from the corresponding Microsoft endpoints. However, the AWS ALB does not return all of these tokens! The ALB only
returs the original access token mapped to a header called x-amzn-oidc-accesstoken. It does not return the original ID
token that it received from Entra ID. It is important to know this information beforehand if the system depends on the
ID tokoen for authorization as well in addition to authentication since it contains the group memberships of the user as
a claim called groups. The AWS ALB generates a separate JWT instead called x-amzn-oidc-data that contains the user
claims received from the IdP user info endpoint and is signed by the ALB itself. The ALB forwards the x-amzn-oidc-*
headers to its target group:
x-amzn-oidc-accesstoken: The access token from the token endpoint.x-amzn-oidc-identity: The subject field (sub) from the user info endpoint.x-amzn-oidc-data: The user claims, in JSON web tokens (JWT) format.
According to the official documentation: The Application Load Balancer creates a new access token when authenticating a user and only passes the access tokens and claims to the backend, however it does not pass the ID token information.
AWS ALB AuthN and Backend AuthZ
Since we are managing users in Entra ID, we also manage their group memberships there and can use these group
memberships to authorize (AuthZ) the user in our app. The AWS ALB is responsible for authenticating (AuthN) the user,
but not for authorizing (AuthZ) it. What is missing in the ALB tokens that would allow our backend to do AuthZ using
Entra ID? It is the group memberships of the user that is present in the original ID token as a roles or groups
claim.
The roles and groups claims that our backend application needs is not passed to the ALB x-amzn-oidc-data since the
ALB fetches the user claims from the user info endpoint. The Entra ID user info endpoint does not return the group
memberships of the user. This means that the backend application does not receive any information about the Entra ID
group memberships of the logged-in user and cannot decide if the user has admin or developer permissions, for example.
Here is an example of a decoded x-amzn-oidc-data JWT that contains the user claims:
| |
The ALB signed the token as shown in the signer claim in the header but Entra ID issued it as shown in the iss
claim. The payload contains the name and email address of the user but no information about the group memberships.
The x-amzn-oidc-access header contains the access token and the x-amzn-oidc-identity header contains the subject
field (sub) which is already present in x-amzn-oidc-data.
Here is a decoded ID token retrieved directly from Entra ID after successful authentication:
| |
Notice the groups claim that contains the group memberships of the user as group IDs in addition to the roles
claim that contains the mapped roles of the user.
User Group Memberships
We need to get the group memberships of the user in order to authorize the user in our backend application. Here is a sequence diagram of the problem definition:
sequenceDiagram
User ->> ALB: Authenticate
ALB ->> Entra ID: Entra ID authentication
Entra ID -->> ALB: ID and Access Tokens
Note right of Backend: ALB creates its own tokens x-amzn-oidc-*
ALB ->> Backend: x-amzn-oidc-accesstoken, x-amzn-oidc-identity, x-amzn-oidc-data
Backend --x User: admin user or developer? Missing group memberships in x-amzn-oidc-data
We have two practical possible solutions that can be used to get the group memberships of the user:
- call the Graph API from the backend using the original access token
- use Cognito federated OIDC with custom attributes mapped to the
groupsclaim in the ID token
Group Memberships from the Graph API using the Backend
One way to get the group memberships of the user is to use the Microsoft Graph API. Since the ALB returns the
x-amzn-oidc-accesstoken header that contains the original access token, we can use it to call the Microsoft Graph API.
Namely, we can call the List member of endpoint to get the group memberships of the current user as shown in the
sequence diagram below:
sequenceDiagram
User ->> ALB: Authenticate
ALB ->> Entra ID: Entra ID authentication
Entra ID -->> ALB: ID and Access Tokens
ALB ->> Backend: x-amzn-oidc-accesstoken, x-amzn-oidc-identity, x-amzn-oidc-data
Note right of Backend: Use x-amzn-oidc-accesstoken for Graph API call
Backend ->> Graph API: GET /me/memberOf
Graph API -->> Backend: User Groups
Backend -->> User: Authorize user according to group memberships
Here is an example of a decoded x-amzn-oidc-accesstoken JWT that contains the original access token issued by Entra
ID:
| |
Here’s the response from the user info endpoint fromm where the ALB retrieves the user claims:
| |
Here’s an example of a call to the Graph API to get the basic information of the current user:
| |
Here’s an example of a call to the Graph API to get the group memberships of the current user:
| |
The response contains a list of maps that contain the group ID and some other information of the group. One can also cache the results for 5 to 10 minutes using a DynamoDB table. This way, a new request to the Graph API would only take place in case of a cache lookup miss in DynamoDB. Keep in mind that this means any changes to the group memberships of the user inside Entra ID will not be reflected in the application until the cache is cleared after 5 or 10 minutes.
Group Memberships using Cognito Federated OIDC
This is addressed in our second blog post on this topic.
Misc.
Here are some important miscellaneous topics to consider when dealing with ALB authentication.
ALB JWT Signature Verification
The backend has to verify the signature of the ALB JWTs before doing any authorization based on the claims. Validate
that the signer field contains the expected ALB ARN. You can also
use AWS JWT Verify
AWSELBAuthSessionCookie
The ALB creates an encrypted cookie named AWSELBAuthSessionCookie after successful authentication. If the cookie is
present and valid, the ALB will not authenticate the user again. Otherwise, the ALB will redirect the user to the
authentication page. Only the ALB can decode the cookie data, and the user cannot not see any meaningful information in
the cookie. Here is an example of the cookie value with some content in the middle redacted:
| |
ALBeast Security Bypass
We have seen in the previous section a decoded x-amzn-oidc-data JWT:
- it was signed by the ALB
- it was issued by Entra ID
- it did not contain an aud claim
This has led to the discovery of ALBeast. More information can be
found here.
In order to make sure that you are not a victim of this vulnerability:
- make sure that the ALB targets accept traffic only from the ALB by referencing the ALB security group in the inbound rules of the target security group
- implement signature validation for the ALB JWT and confirm the signature by your ALB ARN
An official AWS spokesperson response to this according to The Hacker News:
It is incorrect to call this an authentication and authorization bypass of AWS Application Load Balancer (ALB) or any other AWS service because the technique relies on a bad actor already having direct connectivity to a misconfigured customer application that does not authenticate requests. We recommend customers configure their applications to only accept requests from their ALB by using security groups and by following the ALB security best practices. A small fraction of a percent of AWS customers have applications potentially misconfigured in this way, significantly fewer than the researchers’ estimate. We have contacted each one of these customers directly to share best practices for configuring applications which use ALB.
Conclusion
In this post, we have seen how to configure Entra ID OIDC authentication for an AWS ALB listener. We have also seen one way on how to get the group memberships of the user in the backend application by using the Microsoft Graph API. In part 2 of this series, we will see how to use Cognito Federated OIDC to achieve the same result. We also discussed some miscellaneous security topics that need to be considered.
References:
- Authenticate users using an Application Load Balancer
- Security best practices when using ALB authentication
- List a user’s direct memberships
- awslabs/aws-jwt-verify
- r/aws how_to_get_id_token_claims_using_aws_oidc
- dev.to - deploy-oidc-authentication-on-aws-with-no-coding-using-aws-cognito-and-application-load-balancer
- doit.com blog aws-application-load-balancers-oidc-flow-http-transactions
- ALBeast: a simple misconfiguration to a complete authentication bypass
- SA Talks - ALBeast Security Vulnerability Advisory
- New ‘ALBeast’ Misconfiguration Exposes Weakness in AWS Application Load Balancer