Integrating AWS Cognito with Suomi.fi and others eIDAS services via SAML interface

AWS Cognito and Azure AD both support SAML SSO integration but neither supports encryption and signing of SAML messages. Here is a solution for a problem that all European public sector organizations are facing.

In the end of 2018, The Ministry of Finance of Finland aligned how Finnish public sector organizations should treat public cloud environments. To summarize, a “cloud first” -strategy. Everybody should use the public cloud, and if they don’t, there must be a clear reason why not. The most typical reason is, of course, classified data. The strategy is an extremely big and clear indication of change regarding how organizations should treat the public cloud nowadays.

To move forward fast, most applications require an authentication solution.  In one of my customer projects I was requested to design the AWS cloud architecture for a new solution with a requirement for strong authentication of citizens and public entities. In Finland, for public sector organizations there exists an authentication service called Suomi.fi (Suomi means Finland) to gain trusted identity. It integrates banks etc. to a common platform.  The service is following strictly the Electronic Identification, Authentication and Trust Services (eIDAS) standard. Currently, and at least in short term future perspective, the Suomi.fi supports only SAML integration.

eIDAS SAML with AWS Cognito – Not a piece of cake

Okay, that’s fine. The plan was to use AWS Cognito for strong security boundary for applications and it supports “the old” SAML integration. But in few hours later, I started to say No, Why and What. The eIDAS standard requires encrypted and signed SAML messaging. Sounds reasonable. However, soon I found out that AWS Cognito (or for example Azure AD) does not support it. My world collapsed for a moment. This was not going to be as easy as I thought.

After I contacted to AWS partner services and Suomi.fi service organization, it was clear that I need to get my hands dirty and build something for this. In Solita we are used to have open discussions and transfer information from mouth to mouth between project. So, I already knew that there are at least a couple of other projects that are facing the problem. They also are using AWS Cognito and they also need to integrate with eIDAS authentication service. This made my journey more fascinating because I could solve a problem for multiple teams.

Solution architecture

Red hat JBoss Keycloak is the star of the day

Again, because open discussion my dear colleague Ari from Solita Health (see how he is doing while these remote work period) pointed out that I should look into the product called Keycloak. After I found out that it is backed by Red Hat JBoss, I knew it has a strong background. The Keycloak is a single sign on solution which supports e.g. SAML integration for eIDAS service and OpenID for AWS Cognito.

Here is simple reference architecture from the solution account setup (click to zoom):

The solution is done with DevOps practices. There is one Git repository for Keycloak Docker image and one for AWS CDK project. The AWS CDK project is provisioning the square area components with dash line to the AWS account (and e.g. CI/CD pipelines not shown in the picture). The rest is done by the actual IaC-repository of each project because it varies too much.

We run Keycloak as a container in AWS Fargate service which has at least two instances always running in two availability zone in the region. The Fargate service integrates nicely with AWS ALB, for example if one container is not able to answer to health check request, it will not receive any traffic and soon it will be replaced by another container automatically.

Multiple keycloak instances forms a cluster. They need to share data between each other via TCP connection. The Keycloak uses jgroups to form the cluster. In the solution, the Fargate service register (and deregister) the new container to AWS Cloud Map service automatically and provides DNS interfaces to find out which instances are up and healthy. Keycloak uses “DNS PING” query method by jgroups to search others via Cloud Map DNS records.

The other thing what Keycloak clusters need is the database. In this solution we used AWS Aurora PostgreSQL PaaS database service.

The login flow

The browser is the key integrator element because it is redirected multiple times with payload from service to another. If you don’t have previous knowledge how  SAML works, check Basics of SAML Auth by Christine Rohacz.

The (simplified) initial login flow is described below. Yep, even it is hugely simplified, it still has so many steps.

  1. User enters access the URL of the application. The application is protected by AWS Application Load Balancer and its listener rule requires user to have valid AWS Cognito session. Because the session it is missing, user is redirected to the AWS Cognito domain.
  2. The AWS Cognito receives request and because no session found and identity provider is defined, it forwards the user again to the Keycloak URL.
  3. The Keycloak receives the request and because no session is found and SAML identity provider is defined, it forwards the user again to the Suomi.fi authentication service with signed and encrypted SAML AuthnRequest.
  4. After user has proven his/her identity at Suomi.fi service, the Suomi.fi service redirects user back to the Keycloak service.
  5. The Keycloak verifies and extracts the SAML message and its attributes, and forwards user back to the AWS Cognito service
  6. The AWS Cognito verifies the OpenID message and asks more user information via secret from Keycloak and finally redirects the user back to the Application ALB.
  7. The application’s ALB receives the identity and finally redirects the user back to the original path of the application’s ALB

Now user have session within the application ALB (not with the Keycloak ALB) for several hours.

The application receives internally few extra headers

The application ALB adds two JWT tokes via x-amzn-oidc-accesstoken and x-amzn-oidc-data headers to each request it sends to the backend. From those headers, the application can easily access to the information who is logged in and other information about the user profile in AWS Cognito. Those headers are only passed between ALB and the application.

Here is example of those headers:

Notice: the data is imaginary and for testing purpose by Suomi.fi

x-amzn-oidc-accesstoken: {
    "sub": "765371aa-a8e8-4405-xxxxx-xxxxxxxx",
    "cognito:groups": [
        "eu-west-1_xxxxxx"
    ],
    "token_use": "access",
    "scope": "openid",
    "auth_time": 1591106167,
    "iss": "https://cognito-idp.eu-west-1.amazonaws.com/eu-west-1_xxxxxx",
    "exp": 1591109767,
    "iat": 1591106167,
    "version": 2,
    "jti": "xxxxx-220c-4a70-85b9-xxxxxx",
    "client_id": "xxxxxxx",
    "username": "xxxxxxxxx"
}

x-amzn-oidc-data: {
    "custom:FI_VKPostitoimip": "TURKU",
    "sub": "765371aa-a8e8-4405-xxxxx-xxxxxxxx",
    "custom:FI_VKLahiosoite": "Mansikkatie 11",
    "custom:FI_firstName": "Nordea",
    "custom:FI_vtjVerified": "true",
    "custom:FI_KotikuntaKuntanro": "853",
    "custom:FI_displayName": "Nordea Demo",
    "identities": "[{\"userId\":\"72dae55e-59d8-41cd-a413-xxxxxx\",\"providerName\":\"Suomi.fi-kirjautuminen\",\"providerType\":\"OIDC\",\"issuer\":null,\"primary\":true,\"dateCreated\":1587460107769}]",
    "custom:FI_lastname": "Demo",
    "custom:FI_KotikuntaKuntaS": "Turku",
    "custom:FI_commonName": "Demo Nordea",
    "custom:FI_VKPostinumero": "20006",
    "custom:FI_nationalIN": "210281-9988",
    "username": "Suomi.fi-kirjautuminen_72dae55e-59d8-41cd-a413-xxxxxx",
    "exp": 1591106287,
    "iss": "https://cognito-idp.eu-west-1.amazonaws.com/eu-west-1_xxxxxx"
}

Security

There are multiple security elements and best practices in use also for this solution. For example, each environment of each system has their own AWS account as the first security boundary. So, there will be separate Keycloak installation for each environment.

There are few secret strings that are generated to the AWS Secret Manager and used by Keycloak service via secret injection in runtime by Fargate task definition. For example, the OpenID secret is generated and shared via AWS Secret Manager and it is newer published to code repository etc.

The Keycloak service is only published by the Suomi.fi realm. Eg. the default admin panel from default realm is not published to the internet. Realm is a Keycloak concept to have multiple solutions inside a single Keycloak system with boundaries.

The Keycloak stores user profiles but it can be automatically cleaned if required by the project.

About me

I’m a cloud architect/consultant for public sector customers in Finland in Solita. I have a long history with AWS. I found a newsletter that in September 2008 EBS service was announced.  Me and my brother were excited and commenting “finally persistent storage for EC2”. The EC2 were extended to Europe a month later. I know for sure that at least from 2008 I have used AWS services. Of course, the years have not been the same, but it is nice to have some memories with you.