<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>iam on Digi Hunch</title><link>https://www.digihunch.com/tag/iam/</link><description>Recent content in iam on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Wed, 02 Apr 2025 10:34:44 -0400</lastBuildDate><atom:link href="https://www.digihunch.com/tag/iam/index.xml" rel="self" type="application/rss+xml"/><item><title>Managing EC2 instances across accounts with Ansible</title><link>https://www.digihunch.com/2024/05/managing-ec2-instances-across-aws-accounts-ssm/</link><pubDate>Mon, 27 May 2024 14:00:22 -0400</pubDate><guid>https://www.digihunch.com/2024/05/managing-ec2-instances-across-aws-accounts-ssm/</guid><description>&lt;img src="https://www.digihunch.com/wp-content/uploads/2025/04/feature-ec2-ssm.webp" alt="Featured image of post Managing EC2 instances across accounts with Ansible" /&gt;&lt;p class="wp-block-paragraph"&gt;I regard &lt;a href="https://aws.amazon.com/systems-manager/"&gt;AWS Systems Manager&lt;/a&gt; as &lt;a href="https://www.digihunch.com/2023/10/the-systems-manager-hodgepodge/"&gt;omnipotent&lt;/a&gt;. Nonetheless, there are a few reasons that makes &lt;a href="https://www.ansible.com/"&gt;Ansible&lt;/a&gt; still a prevalent VM (EC2) management tool over Systems Manager (SSM). First, organizations already vested in their custom Ansible &lt;a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html"&gt;roles&lt;/a&gt; and &lt;a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_intro.html"&gt;playbooks&lt;/a&gt; want to reuse, and expand their assets in Ansible. The benefit is consistency in the VM management, over time, and across platforms (AWS, on-prem, Azure, etc). Even for AWS shops, in the last few years many enterprises have adopted &lt;a href="https://www.digihunch.com/2022/12/landing-zone-in-aws/"&gt;AWS landing zone&lt;/a&gt; with the &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/benefits-of-using-multiple-aws-accounts.html"&gt;multiple AWS account&lt;/a&gt; prescriptive pattern. However AWS Systems Manager still lacks integration with &lt;a href="https://docs.aws.amazon.com/organizations/latest/userguide/services-that-can-integrate-ssm.html"&gt;AWS Organization&lt;/a&gt; (except for a few non-core capabilities). This creates the demand of managing EC2 instances across AWS accounts. In this post, we propose a secure method to manage a fleet of EC2 instances from multiple AWS accounts, using Systems Manager . It also enables connectivity from an Ansible control node.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Prerequisites&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This proposal ties a few CLI tools together, including AWS CLI, SSH, Ansible, etc. It also requires the cloud engineer to understand how they work. I&amp;#8217;ll start with the choice of the tools.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Above, I discussed the benefit of Ansible. Since Ansible operates on SSH, we&amp;#8217;ll still have to use SSH tools. Even though SSM agent provides a way to connect to EC2 instance without requiring an RSA key pair, we still need SSH since it is a well-established &lt;a href="https://datatracker.ietf.org/doc/html/rfc4253"&gt;industry standard&lt;/a&gt; (RFC4253) and the foundation of Ansible. These two technologies are not mutually exclusive. In fact, the SSM agent provides a secure enhancement to the operation with SSH. Traditionally, on each EC2 instance we&amp;#8217;d have to run SSHD services which opens TCP port 22 (or alternative TCP port as configured). For authentication we favour key pair over password but the open port is still an attack surface vulnerable to brute force and DDoS attacks. For EC2 instances on private networks there is no reachability to the instance&amp;#8217;s SSH port unless the bastion is also in a connected network. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As I cover in a &lt;a href="https://www.digihunch.com/2023/10/the-systems-manager-hodgepodge/"&gt;post&lt;/a&gt;, the SSM Session Manager comes in handy. The SSM agent operates from the instance and communicate outbound to AWS backend. Since the SSM agent runs under a privileged user on the OS, you can perform OS-level commands through SSM. Further, AWS developed a &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html"&gt;Session Manager plugin&lt;/a&gt; with AWS CLI, allowing AWS CLI as a proxy command when making an SSH connection. Therefore SSM enables SSH connection without requiring port 22 to be open. In addition we&amp;#8217;ll need to use RSA key pair as required for SSH, which is also an improvement to the security posture. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;That explains the dependent tools. On the Ansible &lt;a href="https://docs.ansible.com/ansible/latest/network/getting_started/basic_concepts.html#control-node"&gt;control node&lt;/a&gt;, apart from Ansible itself, we need the latest version of AWS CLI with the &lt;a href="https://docs.aws.amazon.com/organizations/latest/userguide/services-that-can-integrate-ssm.html"&gt;Session Manager plugin&lt;/a&gt;, we need to configure AWS CLI properly to connect to EC2 instances across multiple AWS accounts. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Configure AWS CLI&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This section discusses how to configure AWS CLI. I have a couple of handy aliases for productivity but they are not essential. For example, I often need to check the IAM identity making the call, and I often need to list out all profiles configured. So I added the following two entries in the &lt;code&gt;~/.aws/cli/alias&lt;/code&gt; file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[toplevel]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;whoami = sts get-caller-identity --no-cli-pager --output yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;profile = configure list-profiles&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;With that I have an alias to check IAM identity and available profiles. Then we can start configuring the profiles for CLI (in the file &lt;code&gt;~/.aws/config&lt;/code&gt;). Since we&amp;#8217;ll be working with multiple AWS accounts, we have to manage multiple CLI profiles, which implies that:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;We better use the &lt;code&gt;--profile&lt;/code&gt; switch to explicitly specify profile being used, instead of relying on the &lt;code&gt;AWS_PROFILE&lt;/code&gt; environment variable;&lt;/li&gt;&#10;&lt;li&gt;As a security best practice, we should not configure profiles with &lt;a href="https://docs.aws.amazon.com/sdkref/latest/guide/access-iam-users.html"&gt;long-term IAM credential&lt;/a&gt; in the config file;&lt;/li&gt;&#10;&lt;li&gt;We must ensure the CLI doesn&amp;#8217;t prompt for log-in every time we switch profile&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To satisfy #3 there are many ways but we&amp;#8217;ll discuss two: using cross-account IAM role, and using AWS SSO.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Bonus point if you enable &lt;a href="https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-completion.html"&gt;auto-complete&lt;/a&gt; for AWS CLI.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Configure AWS CLI Profiles&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With cross-account IAM role, the idea, is that the client start with one IAM identity, and use that IAM identity to assume roles on several other accounts. The configuration looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[profile jump_account]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;credential_process = /opt/bin/awscreds-custom --username helen&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[profile target_account_1]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;role_arn = arn:aws:iam::123456789011:role/OrganizationAccountAccessRole&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;source_profile = jump_account&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[profile target_account_2]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;role_arn = arn:aws:iam::123456789012:role/OrganizationAccountAccessRole&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;source_profile = jump_account&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In this example, you start with an validated identity in the jump account, then assume a privileged IAM role named OrganizationAccountAccessRole on the target accounts. Typically such IAM roles are pre-configured (e.g. in an multi-account landing zone) with appropriate trust policy to allow principals from the jump account. Once you&amp;#8217;re validated as the IAM identity in the jump account, then you can use profiles for target accounts without being prompted for credentials again.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If your have configured IAM Identity Center for the multi-account environment, consider an alternative approach using sso login. The configuration usually looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[sso-session sso]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sso_start_url = https://myorg.awsapps.com/start/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sso_region = us-east-1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sso_registration_scopes = sso:account:access&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[profile target_account_1]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sso_session = sso&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sso_account_id = 123456789011&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sso_role_name = AWSAdministratorAccess&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[profile target_account_2]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sso_session = sso&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sso_account_id = 123456789012&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sso_role_name = AWSAdministratorAccess&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This is often used by human users with SSO credential. In this example, to authenticate the sso session, start with command &amp;#8220;aws sso login&amp;#8221; . Then you can use all profiles by specifying &lt;code&gt;--profile&lt;/code&gt; switch without having to log in again.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Configure SSH to EC2 via SSM&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The EC2 instance must connect to Systems Manager &lt;a href="https://docs.aws.amazon.com/general/latest/gr/ssm.html"&gt;endpoint&lt;/a&gt;, before one can SSH to the instance using the plugin. Once connected, you should find the instance in &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/fleet.html"&gt;Fleet Manager&lt;/a&gt;. For this to happen, there are a few conditions. First, the instance must be able to reach the endpoint, either via public Internet, or via VPC interface endpoints if in a private subnet. Second, the instance profile must contain an IAM role with appropriate permissions. We can use AWS managed policy AmazonSSMManagedInstanceCore in the role. In addition, if we record the SSM session to an S3 bucket with encryption the instance profile must have permission to use the encryption key.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="511" height="141" src="https://www.digihunch.com/wp-content/uploads/2024/05/ssh-ssm.webp" alt="" class="wp-image-12985" srcset="https://www.digihunch.com/wp-content/uploads/2024/05/ssh-ssm.webp 511w, https://www.digihunch.com/wp-content/uploads/2024/05/ssh-ssm-300x83.webp 300w" sizes="auto, (max-width: 511px) 100vw, 511px" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;SSM agent uses the IAM role. The agent runs as a service on Linux or Windows machines. Many AWS managed AMIs come with SSM agent pre-installed. If that is not the case, you&amp;#8217;d install the agent in your own AMI, or in user data which requires downloading the installer. With these configuration you&amp;#8217;d be able to connect to the instance via SSM. We can use AWS &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-sessions-start.html#sessions-start-cli"&gt;CLI SSM command&lt;/a&gt;, or AWS web console to start an SSH session. To use SSH CLI utility, we install the &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/plugin-version-history.html"&gt;session manager plugin&lt;/a&gt; along with AWS CLI on the SSH client machine. We also specify a public key for EC2 instance and run ssh command with the private key. The SSH configuration needs a configuration such as:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;host i-* mi-*&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ProxyCommand sh -c &amp;#34;aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters &amp;#39;portNumber=%p&amp;#39;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; User ec2-user&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IdentityFile ~/.ssh/id_rsa&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;With this entry, you may directly SSH by instance ID (usually starting with i-* and mi-*), and the specified Proxy Command with SSM session document AWS-StartSSHSession will be invoked. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Configure Ansible Inventory&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can SSH to an instance (without port 22 open, on top of SSM) using the method above. Similarly, we can also configure Ansible to connect to the instance, without port 22. The inventory configuration looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mytest:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; instance1:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible_host: i-00aabbffcc7755221&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible_user: ubuntu&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible_ssh_common_args: -o ProxyCommand=&amp;#34;aws ssm start-session --target %h --document-name AWS-StartSSHSession --profile target_account_1&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; instance2:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible_host: i-eedd88ff66aa22442&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible_user: ubuntu&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible_ssh_common_args: -o StrictHostKeyChecking=no -o ProxyCommand=&amp;#34;sh -c \&amp;#34;aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters &amp;#39;portNumber=%p&amp;#39; --profile target_account_2 \&amp;#34;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Note I used two patterns for ansible_ssh_common_args that are similar. Both works. Each entry references its own profile. This is necessary because Ansible does not have the knowledge which instance belongs to which profile&amp;#8217;s account.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another way to get Ansible connect to instances is to use the connection plugin &lt;a href="https://docs.ansible.com/ansible/latest/collections/community/aws/aws_ssm_connection.html"&gt;community.aws.aws_ssm&lt;/a&gt;, by specifying ansible_connection: aws_ssm (e.g. as host variable) and other required variables (e.g. profile). This method does not require SSH channel but it requires an S3 bucket, and hence IAM permission on the controller node.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Ansible supports dynamic inventory, in three ways: inventory source file (with existing plugin), custom inventory plugin, and inventory script (in Python). Take source file as an example for EC2, add the followings as the content of aws_ec2.yaml:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;plugin: amazon.aws.aws_ec2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# Attach the default AWS profile&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;aws_profile: target_account_1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;compose:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible_host: instance_id&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible_user: &amp;#34;&amp;#39;ubuntu&amp;#39;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansible_ssh_common_args: &amp;#34;&amp;#39;-o ProxyCommand=\&amp;#34;aws ssm start-session --target %h --document-name AWS-StartSSHSession --profile target_account_1 \&amp;#34;&amp;#39;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then we can display the rendered inventory list, and Ansible-ping the instances. &lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible-inventory -i aws_ec2.yaml --list -y | less&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ansible all -i aws_ec2.yaml -m ping&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;For more flexibility, for the composed variables, we could use jinja2 expression to generate the value. In both ways, we produce an inventory source per profile using the &lt;a href="https://vexpose.blog/2023/11/20/ansible-dynamic-inventory-tutorial/"&gt;built-in inventory plugin&lt;/a&gt; aws_ec2. For greater flexibility, such as consolidating instances from all accounts into a single inventory, consider writing your own&lt;a href="https://www.redhat.com/sysadmin/ansible-dynamic-inventories"&gt; inventory script&lt;/a&gt;, or even &lt;a href="https://www.redhat.com/sysadmin/ansible-plugin-inventory-files"&gt;own inventory plugin&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Conclusion&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this post we propose a way to manage instances across AWS accounts. Two main challenges are establishing the communication channel (SSH on top of SSM) and generating inventory data in Ansible. Some AWS services can generate inventory data, such as &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-inventory-datasync.html"&gt;resource data sync&lt;/a&gt; in Systems Manager, or using AWS Config Aggregator. It is unfortunate that neither way produces the inventory data in a format that is directly compatible with Ansible inventory. Therefore, you might have to create a custom Ansible dynamic inventory script (in Python) that reads from the inventory data from AWS Config Aggregator (which supports AWS Organization). The side benefit of this script is that it is usually faster than the built-in &lt;a href="https://docs.ansible.com/ansible/latest/collections/amazon/aws/aws_ec2_inventory.html"&gt;aws_ec2&lt;/a&gt; inventory plugin.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2024/03/public-key-infrastructure-3-of-3-use-cases/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Public Key Infrastructure 3 of 3 – PKI Implementation&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2024/07/iam-roles-for-any-workload/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;IAM Roles for any workload&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Workload Identity on Kubernetes 2 of 2 – EKS</title><link>https://www.digihunch.com/2024/01/workload-identity-on-kubernetes-2-of-2-eks-and-rosa-on-aws/</link><pubDate>Mon, 08 Jan 2024 00:05:00 -0400</pubDate><guid>https://www.digihunch.com/2024/01/workload-identity-on-kubernetes-2-of-2-eks-and-rosa-on-aws/</guid><description>&lt;img src="https://www.digihunch.com/wp-content/uploads/2025/04/feature-workload-identity-eks.webp" alt="Featured image of post Workload Identity on Kubernetes 2 of 2 – EKS" /&gt;&lt;p class="wp-block-paragraph"&gt;I discussed in my &lt;a href="https://www.digihunch.com/2023/12/workload-identity-on-kubernetes-1-of-2-aks/"&gt;previous post&lt;/a&gt; on workload identity and dived into how it works in AKS (Azure Kubernetes Service). In this post I will continue the topic with AWS as the example. From the perspective of CSP, we consider any running process on the cloud resource as workload. Therefore, I&amp;#8217;ll start with control plan and node identities. From the perspective of a Kubernetes platform, the term workload mostly refers to applications running in Pods. So later in this article I&amp;#8217;ll distinguish two mechanisms for Pod Identity: IRSA and EKS Pod Identity. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;EKS Control Plane and Node Identity&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AWS directly associate an IAM role with EKS control plane and an IAM role with each node group. We don&amp;#8217;t need an extra step of assigning a &amp;#8220;managed identity&amp;#8221; (as in Azure) to a cluster or to a node group ( and then bind a role to the identity). You can find this pattern from Terraform &lt;a href="https://github.com/digihunch/cloudkube/tree/main/eks"&gt;code&lt;/a&gt;. Each &lt;code&gt;aws_eks_node_group&lt;/code&gt; resource has a &lt;code&gt;node_role_arn&lt;/code&gt; attribute to link to its IAM role, and a &lt;code&gt;cluster_name&lt;/code&gt; attribute to link to the cluster. Each &lt;code&gt;aws_eks_cluster&lt;/code&gt; resource has a &lt;code&gt;role_arn&lt;/code&gt; attribute for cluster&amp;#8217;s permission. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The cluster&amp;#8217;s IAM role is usually bound to managed policies like &lt;code&gt;AmazonEKSVPCResourceController&lt;/code&gt; and &lt;code&gt;AmazonEKSClusterPolicy&lt;/code&gt;. The IAM role that is assigned to the node group is the exact IAM role of the instance profile of each node. The kubelet process on the nodes are the main users of this role and the permission should not be broader than what it needs to do. This role usually have a few managed policies such as &lt;code&gt;AmazonEKSWorkerNodePolicy&lt;/code&gt;, &lt;code&gt;AmazonEKS_CNI_Policy&lt;/code&gt;, &lt;code&gt;AmazonSSMManagedInstanceCore&lt;/code&gt; and &lt;code&gt;AmazonEC2ContainerRegistryReadOnly&lt;/code&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The node role applies to self-managed node and managed node. When using Fargate to provide computing capacity, each Fargate profile will use its own IAM role, to connect to the cluster and pull container images. This IAM role is known as Pod Execution Role. For a private cluster, the place to run the command would be a bastion host with connectivity to the cluster&amp;#8217;s API endpoint. Refer to &lt;a href="https://www.digihunch.com/2023/06/connect-kubectl-to-private-kubernetes-cluster-in-eks-and-aks/"&gt;this&lt;/a&gt; post about the connectivity to private cluster.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;IAM Role for Service Account (IRSA)&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When AWS launched EKS in 2018, &lt;a href="https://github.com/uswitch/kiam"&gt;Kiam&lt;/a&gt; was a popular open-source project to grant Pods access to AWS resources. In 2019, AWS &lt;a href="https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts/"&gt;introduced the official mechanism&lt;/a&gt;, IRSA (IAM Role for Service Account). IRSA ties a Kubernetes identity (in the form of Service Account) to an IAM role in AWS. IAM allows creation of web identity based on OIDC. EKS can act as an OIDC issuer. &lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full is-resized"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="545" src="https://www.digihunch.com/wp-content/uploads/2024/01/eks-oidc.webp" alt="" class="wp-image-12976" style="width:458px;height:auto" srcset="https://www.digihunch.com/wp-content/uploads/2024/01/eks-oidc.webp 1024w, https://www.digihunch.com/wp-content/uploads/2024/01/eks-oidc-300x160.webp 300w, https://www.digihunch.com/wp-content/uploads/2024/01/eks-oidc-768x409.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This requires a few points of configurations, via the cluster API and via cloud the endpoint. The &lt;code&gt;eksctl&lt;/code&gt; utility makes it simple with two commands:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ eksctl utils associate-iam-oidc-provider \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --cluster $CLUSTER_NAME \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --approve&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ eksctl create iamserviceaccount \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --cluster=$CLUSTER_NAME \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --namespace=kube-system \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --name=aws-load-balancer-controller \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --role-name AmazonEKSLoadBalancerControllerRole \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --attach-policy-arn=arn:aws:iam::112233445566:policy/AWSLoadBalancerControllerIAMPolicy \&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --approve&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt; The first command creates an OIDC web identity integrated with the EKS cluster, and the second creates a Service Account in Kubernetes and links it to the identity, and assign an IAM policy. These two commands must run under certain conditions. The AWS CLI identity for first command requires the the permission to add OIDC provider. The second needs the permission to create an IAM role. In addition, it requires kube API access to the cluster. So the command needs to run from an environment that can access both the cluster&amp;#8217;s API and AWS API. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The IAM identity provider is somewhat similar to a managed identity with OIDC federated credential in Azure. However, unlike managed identity, here in AWS we cannot create the OIDC identity until after the cluster creation. In other words, the OIDC web identity&amp;#8217;s lifecycle does not decouple with the cluster lifecycle. We have to create a new web identity every time we create a new EKS cluster. In large organizations, the permission to create a new web identity is highly restricted.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;EKS Pod Identity&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are a few other limitations with IRSA. As &lt;a href="https://aws.amazon.com/blogs/containers/amazon-eks-pod-identity-a-new-way-for-applications-on-eks-to-obtain-iam-credentials/"&gt;this&lt;/a&gt; blog post suggests:&lt;/p&gt;&#10;&lt;blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Further, cluster administrators have to update the IAM role trust policy each time the role is used in a new cluster during scenarios like blue-green upgrades or failover testing. Additionally, as customers grow their EKS cluster footprint, due to the per cluster OIDC provider requirement in IRSA, customers run into the per account OIDC provider limit. Similarly, as they scale the number of clusters or Kubernetes namespaces in which an IAM role is used, they run into IAM trust policy size limit, which makes them duplicate the IAM roles to overcome the trust policy size limit.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AWS brings the new mechanism &amp;#8220;&lt;em&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html"&gt;EKS Pod Identity&lt;/a&gt;&lt;/em&gt;&amp;#8221; at reInvent 2023. In this mechanism, user can hook up an IAM role directly to a Kubernetes service account, without having to resort to a web identity and OIDC integration. Users just need to create a Pod Identity Association, using the CreatePodIdentityAssociation API, with the following parameters:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Cluster name&lt;/li&gt;&#10;&lt;li&gt;Namespace&lt;/li&gt;&#10;&lt;li&gt;ARN of the IAM role&lt;/li&gt;&#10;&lt;li&gt;serviceAccount &lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Both &lt;a href="https://docs.aws.amazon.com/cli/latest/reference/eks/create-pod-identity-association.html"&gt;AWS CLI&lt;/a&gt; and &lt;a href="https://eksctl.io/usage/pod-identity-associations/"&gt;ekscli&lt;/a&gt; already support the CreatePodIdentityAssociation API. Before creating a Pod Identity Association, we need to install the add-on &amp;#8220;Amazon EKS Pod Identity Agent&amp;#8221;, and ensure that the &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/pod-id-agent-setup.html"&gt;node roles&lt;/a&gt; have the permission. That is because the agent needs to use AssumeRoleForPodIdentity API. We also need an IAM role, with the trust policy principal being &amp;#8220;&lt;em&gt;&lt;strong&gt;pods.eks.amazonaws.com&lt;/strong&gt;&lt;/em&gt;&amp;#8221; and our own choice of &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/access_tags.html"&gt;resource tags&lt;/a&gt; as condition. Note that another implicit prerequisite is that the programming running in the Pod use a newer version of AWS SDK to access cloud resource. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://aws.amazon.com/blogs/containers/amazon-eks-pod-identity-a-new-way-for-applications-on-eks-to-obtain-iam-credentials/"&gt;This&lt;/a&gt; blog post has good details, including a diagram and a walk-through. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Comparison&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Both EKS Pod Identity and IRSA are here to stay. I&amp;#8217;m afraid this is going to create confusions. I put the following table for their comparision:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-stripes"&gt;&lt;table class="has-white-background-color has-background"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;IRSA&lt;/th&gt;&lt;th&gt;EKS Pod Identity&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Pros&lt;/td&gt;&lt;td&gt;&amp;#8211; in use since 2019&lt;br&gt;&amp;#8211; support EKS, EKS-A, ROSA&lt;br&gt;&amp;#8211; support all EKS versions&lt;/td&gt;&lt;td&gt;&amp;#8211; support role session tags&lt;br&gt;&amp;#8211; no dependency on OIDC identity provider&lt;br&gt;&amp;#8211; create an IAM role once for all clusters. the role can be created before cluster&lt;br&gt;&amp;#8211; cross account access through resource policies and chained AssumeRole operation&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cons&lt;/td&gt;&lt;td&gt;&amp;#8211; Cannot create OIDC identity provider, until the cluster is ready&lt;br&gt;&amp;#8211; One OIDC provider per cluster, with the risk of hitting quota&lt;br&gt;&amp;#8211; Trust policy sprawl as more clusters are created&lt;/td&gt;&lt;td&gt;&amp;#8211; the program has to use newer version of SDK. &lt;br&gt;&amp;#8211; ony support EKS&lt;br&gt;&amp;#8211; Pod Identity Agent (DaemonSet) can&amp;#8217;t run on Fargate&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;figcaption class="wp-element-caption"&gt;Compairing IRSA with EKS Pod Identity&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The blog &lt;a href="https://aws.amazon.com/blogs/containers/amazon-eks-pod-identity-a-new-way-for-applications-on-eks-to-obtain-iam-credentials/"&gt;post&lt;/a&gt; also contains a long table for their comparison. In the near future, I will have to check the &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/pod-id-minimum-sdk.html"&gt;SDK version&lt;/a&gt; of a workload in order to assess whether EKS Pod Identity will function. This is a restriction because it depends upon software builder disclosing the SDK version used. The EKS cluster also needs to host daemonSet on a node agent. On the other hand, go with IRSA if portability between EKS and EKS-A and &lt;a href="https://www.digihunch.com/2023/06/kubernetes-paas-and-red-hat-openshift/"&gt;ROSA&lt;/a&gt; is of concern, because the IAM service principal &lt;em&gt;pods.eks.amazonaws.com&lt;/em&gt; is dedicated to EKS. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The blog post also gives the migration step as follows:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Ensure EKS cluster is above 1.24, and install the add-on for EKS pod identity agent. &lt;/li&gt;&#10;&lt;li&gt;Ensure the SDK running in pod meets the version requirement. &lt;/li&gt;&#10;&lt;li&gt;Update the IAM role&amp;#8217;s trust policy with the new principal &amp;#8220;pods.eks.amazonaws.com&amp;#8221;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;So the EKS Pod Identity mechanism still requires an IAM role. It does not required an OIDC identity. The service account connects to IAM role via an agent on the node. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A good design concerns not only functionality, but also streamlined configuration experience. EKS Pod Identity is a great improvement over IRSA heading the right direction. It just came out two months ago so still too early to adopt, especially without knowing the workload details. For now I tend to use pod identity as a backup mechanism when IRSA isn&amp;#8217;t available for some reason. However, I recommend starting to introduce the Pod Identity mechanism for all new EKS clusters and new workloads.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2023/12/workload-identity-on-kubernetes-1-of-2-aks/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Workload Identity on Kubernetes 1 of 2 – AKS&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2024/02/public-key-infrastructure-1-of-3-basics/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Public Key Infrastructure 1 of 3 – Basics&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>OAuth 2.0 and OIDC 2 of 2</title><link>https://www.digihunch.com/2023/07/oauth-2-0-and-oidc-2-of-2/</link><pubDate>Thu, 13 Jul 2023 21:24:01 -0400</pubDate><guid>https://www.digihunch.com/2023/07/oauth-2-0-and-oidc-2-of-2/</guid><description>&lt;img src="https://www.digihunch.com/wp-content/uploads/2025/04/feature-oidc-oauth-2.webp" alt="Featured image of post OAuth 2.0 and OIDC 2 of 2" /&gt;&lt;p class="wp-block-paragraph"&gt;I wrote a &lt;a href="https://www.digihunch.com/2020/03/oauth-and-openid-connect/"&gt;brief&lt;/a&gt; on this topic a while back. Now I need to configure OIDC in a few occasions I decide to dive deeper into the flows this time. As I stated in the last post, Nate Barbettini&amp;#8217;s &lt;a href="https://www.youtube.com/watch?v=996OiexHze0"&gt;presentation&lt;/a&gt; from 2017 was awesome and I viewed it again. Slides are available &lt;a href="https://speakerdeck.com/nbarbettini/oauth-and-openid-connect-in-plain-english"&gt;here&lt;/a&gt;. Another great reference is &lt;a href="https://www.deepnetwork.com/blog//2019/11/08/oauth2-oicd-pkce.html"&gt;this post&lt;/a&gt; from DeepNetwork Developer&amp;#8217;s blog.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-back-channel-and-front-channel"&gt;Back Channel and Front Channel&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To understand why there are several different flows, it is important to understand the difference between back channel and front end channel.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In web development, traditional architecture involves a frontend (e.g. Browser, or any client-side app) and backend server. The web frontend is written in HTML, CSS, JavaScript, etc. There are also web frameworks such as Django, Angular, to save developers time. Backend (server-side) is responsible for storing and organizing data to ensure frontend can function. There might be multiple backend servers, such as session cache, data store, API server, etc. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In a nutshell, server-to-server communication is back channel, and browser-to-server communication is front channel. From security perspective, we regard front-channel as less secure, because we have less control of the location of the front-end and browser is easy to tamper with.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="550" src="https://www.digihunch.com/wp-content/uploads/2023/07/oauth-channels.webp" alt="" class="wp-image-12934" srcset="https://www.digihunch.com/wp-content/uploads/2023/07/oauth-channels.webp 1024w, https://www.digihunch.com/wp-content/uploads/2023/07/oauth-channels-300x161.webp 300w, https://www.digihunch.com/wp-content/uploads/2023/07/oauth-channels-768x413.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;figcaption class="wp-element-caption"&gt;Back Channel and Front Channel (source https://www.okta.com/blog/2019/04/oauth-when-things-go-wrong/)&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For traditional web applications with client-server architecture, when they communicate with third-party servers, they can initiate the communication from their backends, creating a back channel for better security posture. Single-page applications (SPAs) are applications without their own backends. When building an SPA, the front-end developer deals with frontend frameworks such as React, Angular or Next.js. When SPAs have to communicate with third-party APIs, they have to create a front channel. Also the API must support Cross-Origin Resource Sharing (CORS) for the browser to allow the cross-domain communication.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the web development world, there is also Native App (aka Mobile App). Native App to Server communication is also considered back channel as we consider the client-side (Mobile App) secure. Nate&amp;#8217;s talk makes the following recommendation for the flow (grant type) and I rephrase it as below:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-stripes"&gt;&lt;table class="has-white-background-color has-background"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Architecture&lt;/th&gt;&lt;th&gt;Recommendation&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Traditional Web Application (client-server architecture)&lt;/td&gt;&lt;td&gt;There are both front and back channels. Use authorization code flow&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Single Page Application (SPA, e.g. JavaScript) with API backend&lt;/td&gt;&lt;td&gt;Front channel only. Use implicit flow &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Native mobile App&lt;/td&gt;&lt;td&gt;Use authorization code flow with PKCE&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Microservices and APIs (Machine-to-Machine)&lt;/td&gt;&lt;td&gt;Back-channel only. Use Client credentials flow&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The message, is that we should use Authorization Code Flow and use back channel, so long as the required component (backend) exists in the architecture. Note that the terminology for these types of applications may differ. For example, when you try to create an OIDC client for Amazon Cognito user pool, here&amp;#8217;s how it categorizes client capability (app type):&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Public client: A native, browser, or mobil-device app. Cognito API requests are made from user systems that are not trusted with a client secret.&lt;/li&gt;&#10;&lt;li&gt;Confidential client: A server-side application that can securely store a client secret. Cognito API requests are made from a central server.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Get used to different terms describing the same concept.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;OAuth 2.0 and OIDC flows&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The original problem that OAuth 2.0 (RFC6749) addresses is delegated authorization. In all OAuth flows, the authorization server issues an Access Token for the client to carry. The Access Token identifies the scope of resources that its carrier is authorized to access. However, third-party services do not always want to delegate authorization to the identity provider. They often just need identity information and want to perform authorization on their own. The OAuth 2.0 Access Token itself is all about permission and does not care about the identify of its carrier. It is not designed for authentication. OAuth 2.0 does not provide a standard way for Authorization Server to keep identity information of the principal. Many developers needs to address identity issue and they started to (mis)use the AccessToken to store identity information in custom fields, until OIDC came about.&lt;/p&gt;&#10;&lt;figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-3 is-layout-flex wp-block-gallery-is-layout-flex"&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="543" height="335" data-id="12936" src="https://www.digihunch.com/wp-content/uploads/2025/04/oauth-access-token.webp" alt="" class="wp-image-12936" srcset="https://www.digihunch.com/wp-content/uploads/2025/04/oauth-access-token.webp 543w, https://www.digihunch.com/wp-content/uploads/2025/04/oauth-access-token-300x185.webp 300w" sizes="auto, (max-width: 543px) 100vw, 543px" /&gt;&lt;figcaption class="wp-element-caption"&gt;OAuth: Access Token only&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="686" height="516" data-id="12935" src="https://www.digihunch.com/wp-content/uploads/2025/04/oidc-identity-token.webp" alt="" class="wp-image-12935" srcset="https://www.digihunch.com/wp-content/uploads/2025/04/oidc-identity-token.webp 686w, https://www.digihunch.com/wp-content/uploads/2025/04/oidc-identity-token-300x226.webp 300w" sizes="auto, (max-width: 686px) 100vw, 686px" /&gt;&lt;figcaption class="wp-element-caption"&gt;OIDC: Access + Identity Token&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;OIDC is a thin layer (5%) on top of OAuth 2.0 and one important addition is the ID token. The resource server can, in addition to being asked to allow access, now can understand the identity of the principal requesting to access resources from the ID token. The OIDC layer also uses standard set of scopes and proposes a userinfo endpoint for client to get more details about user information. The authorization code flows in OIDC and OAuth2.0 are roughly the same except for the additions.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In terms of the flows supported, the OAuth flows are defined in &lt;a href="https://datatracker.ietf.org/doc/html/rfc6749"&gt;RFC6749&lt;/a&gt;, including the following grants:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Authorization Code grant&lt;/li&gt;&#10;&lt;li&gt;Implicit grant&lt;/li&gt;&#10;&lt;li&gt;Resource Owner Password Credentials grant&lt;/li&gt;&#10;&lt;li&gt;Client Credentials grant&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The classic grant type is Authorization Code. After verifying with user, the Authorization Server fires a call-back to the client to pass the authorization code. The client, then takes the authorization code, along with its client ID and client secret, to fire a request to Authorization server in exchange for Access Token. The implicit grant skips the Authorization Code step and the client gets the Access Token in a one-stop shop via callback over front channel, which is less secure. The other two grants are less often used.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On the OIDC side, the &lt;a href="https://openid.net/specs/openid-connect-core-1_0.html"&gt;specification document&lt;/a&gt; discusses three flows:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Authorization Code Flow (specification 3.1)&lt;/li&gt;&#10;&lt;li&gt;Implicit Flow (specification 3.2)&lt;/li&gt;&#10;&lt;li&gt;Hybrid Flow (specification 3.3)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The original OAuth2.0 flows should only be used in delegated authorization scenario. In most contexts, if we talk about ID token, and if our use case involves authentication, then we&amp;#8217;re talking about OIDC not just OAuth. Since Authorization Code Flow is the classic one, out of all these flows, in the next section we take a closer look at the Authorization Code Flow in OIDC.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Authorization Code Flow in OIDC&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We consider the Authorization Code Flow the baseline flow and others as variations of it owing to architectural limitations. When we mention OIDC we implicitly refers to the Authorization Code Flow unless the context suggests otherwise. Now let&amp;#8217;s zoom in on it:&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="631px" viewBox="-0.5 -0.5 631 581" style="max-width:100%;max-height:581px;"&gt;&lt;defs&gt;&lt;style type="text/css"&gt;@import url(https://fonts.googleapis.com/css?family=Architects+Daughter);&amp;#xa;@import url(https://fonts.googleapis.com/css?family=Architects+Daughter);&amp;#xa;&lt;/style&gt;&lt;/defs&gt;&lt;g&gt;&lt;rect x="0" y="0" width="630" height="580" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 628px; height: 1px; padding-top: 7px; margin-left: 1px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;"&gt;OIDC Authorization Code Flow&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="315" y="19" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold"&gt;OIDC Authorization Code Flow&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="20" y="78.14" width="100" height="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 98px; margin-left: 21px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Resource Owner&lt;br /&gt;(User)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="70" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Resource Owner&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="180" y="78.14" width="100" height="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 98px; margin-left: 181px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Client App&lt;br /&gt;(front &amp;amp; backend)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="230" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Client App&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="340" y="78.14" width="100" height="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 98px; margin-left: 341px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Authorization Server&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="390" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Authorization Se&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 70 570 L 70 118.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/&gt;&lt;path d="M 230 570 L 230 118.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/&gt;&lt;path d="M 390 570 L 390 118.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/&gt;&lt;rect x="500" y="78.14" width="100" height="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 98px; margin-left: 501px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Resource Server&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="550" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Resource Server&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 550 570 L 550 118.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/&gt;&lt;path d="M 70 158.14 L 223.63 158.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 228.88 158.14 L 221.88 161.64 L 223.63 158.14 L 221.88 154.64 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 149px; margin-left: 151px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;1. launch client app&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="151" y="152" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;1. launch client app&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 230 178.14 L 383.63 178.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 388.88 178.14 L 381.88 181.64 L 383.63 178.14 L 381.88 174.64 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 171px; margin-left: 311px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;2. Token Request to /authorize&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="311" y="174" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;2. Token Request to /authorize&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 390 218.14 L 76.37 218.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 71.12 218.14 L 78.12 214.64 L 76.37 218.14 L 78.12 221.64 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 208px; margin-left: 180px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;3. 302 redirect to prompt user to log in&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="180" y="211" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;3. 302 redirect to prompt user to log in&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 70 248.14 L 383.63 248.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 388.88 248.14 L 381.88 251.64 L 383.63 248.14 L 381.88 244.64 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 237px; margin-left: 150px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;4. Authenticate and consent&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="150" y="240" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;4. Authenticate and consent&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 390 310 L 236.37 310" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 231.12 310 L 238.12 306.5 L 236.37 310 L 238.12 313.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 290px; margin-left: 261px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;6. Redirect browser to the callback URI&lt;br /&gt;, with Authorization Code as a parameter &lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="261" y="294" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;6. Redirect browser to the callback URI&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 230 390 L 383.63 390" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 388.88 390 L 381.88 393.5 L 383.63 390 L 381.88 386.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 362px; margin-left: 311px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;7. Request (HTTP) for Tokens&lt;br /&gt;at /token, using Authorization &lt;br /&gt;Code, ClientID and Client Secret&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="311" y="366" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;7. Request (HTTP) for Tokens&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 390 410 L 420 410 L 420 450 L 396.37 450" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 391.12 450 L 398.12 446.5 L 396.37 450 L 398.12 453.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 403px; margin-left: 420px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;8.Validation&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="420" y="406" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;8.Validation&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 390 468.14 L 236.37 468.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 231.12 468.14 L 238.12 464.64 L 236.37 468.14 L 238.12 471.64 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 451px; margin-left: 311px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;9. Response (HTTP) with &lt;br /&gt;IDToken and AccessToken&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="311" y="454" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;9. Response (HTTP) with&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 207.5 30 L 252.5 30 L 252.5 75 L 207.5 75 Z" fill="#ffffff" stroke="none" pointer-events="all"/&gt;&lt;path d="M 212 55.85 L 219.22 55.85 L 219.22 54.21 L 212 54.21 Z M 212 50.79 L 220.66 50.79 L 220.66 49.16 L 212 49.16 Z M 212 45.74 L 222.11 45.74 L 222.11 44.1 L 212 44.1 Z M 238.79 54.21 L 227.48 54.21 L 228.52 50.07 L 239.82 50.07 Z M 227.24 48.43 L 225.39 55.85 L 240.07 55.85 L 241.92 48.43 Z M 241.71 63.33 C 241.68 63.48 241.54 63.59 241.39 63.59 L 221.35 63.59 C 221.22 63.59 221.14 63.52 221.1 63.47 C 221.06 63.42 221.01 63.33 221.03 63.2 L 224.75 45.7 L 245.45 45.7 Z M 225.92 41.41 L 245.96 41.41 C 246.09 41.41 246.17 41.48 246.21 41.53 C 246.25 41.58 246.3 41.67 246.27 41.8 L 245.8 44.06 L 225.09 44.06 L 225.6 41.67 C 225.63 41.52 225.77 41.41 225.92 41.41 Z M 247.48 40.5 C 247.11 40.04 246.55 39.77 245.96 39.77 L 225.92 39.77 C 225 39.77 224.19 40.43 224 41.33 L 219.43 62.86 C 219.31 63.44 219.45 64.04 219.83 64.5 C 220.2 64.96 220.76 65.23 221.35 65.23 L 241.39 65.23 C 242.31 65.23 243.12 64.57 243.31 63.67 L 247.88 42.14 C 248 41.56 247.86 40.96 247.48 40.5 Z" fill="#232f3e" stroke="none" pointer-events="all"/&gt;&lt;path d="M 60 67 C 60 55 60 49 70 49 C 63.33 49 63.33 37 70 37 C 76.67 37 76.67 49 70 49 C 80 49 80 55 80 67 Z" fill="#f5f5f5" stroke="#666666" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;path d="M 230 518.14 L 543.63 518.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 548.88 518.14 L 541.88 521.64 L 543.63 518.14 L 541.88 514.64 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 509px; margin-left: 391px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;10. Issue API Request with Tokens&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="391" y="512" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;10. Issue API Request with Tokens&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 550 548.14 L 236.37 548.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 231.12 548.14 L 238.12 544.64 L 236.37 548.14 L 238.12 551.64 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 539px; margin-left: 348px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;12. Receive API Response&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="348" y="542" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;12. Receive API Response&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 550 530 L 580 530 L 580 540 L 556.37 540" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 551.12 540 L 558.12 536.5 L 556.37 540 L 558.12 543.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 521px; margin-left: 590px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;11. Validation&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="590" y="524" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;11. Validation&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 105 435 C 105 429.48 109.48 425 115 425 L 200 425 C 205.52 425 210 420.52 210 415 C 210 420.52 214.48 425 220 425 L 305 425 C 310.52 425 315 429.48 315 435" fill="none" stroke="#999999" stroke-miterlimit="10" transform="rotate(270,210,425)" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-end; width: 1px; height: 1px; padding-top: 425px; margin-left: 198px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: right;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"&gt;backend&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="198" y="429" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="end"&gt;back&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="470" y="202.5" width="60" height="30" fill="none" stroke="none" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 218px; margin-left: 471px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Authorization&lt;br /&gt;Endpoint&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="500" y="221" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Authorizat&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 414.75 240 C 414.75 234.48 419.23 230 424.75 230 L 451 230 C 456.52 230 461 225.52 461 220 C 461 225.52 465.48 230 471 230 L 497.25 230 C 502.77 230 507.25 234.48 507.25 240" fill="none" stroke="#999999" stroke-miterlimit="10" transform="rotate(90,461,230)" pointer-events="all"/&gt;&lt;rect x="461.25" y="370" width="60" height="30" fill="none" stroke="none" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 385px; margin-left: 462px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Token&lt;br /&gt;Endpoint&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="491" y="389" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Token&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 411 415 C 411 409.48 415.48 405 421 405 L 451 405 C 456.52 405 461 400.52 461 395 C 461 400.52 465.48 405 471 405 L 501 405 C 506.52 405 511 409.48 511 415" fill="none" stroke="#999999" stroke-miterlimit="10" transform="rotate(90,461,405)" pointer-events="all"/&gt;&lt;path d="M 536.89 70 C 535.63 70 535 69 535 68.21 L 535 37.58 C 535 36.62 535.74 35.79 536.76 35.79 L 558.22 35.79 C 558.93 35.79 560 36.37 560 37.63 L 560 68.13 C 560 69.2 559.19 70 558.19 70 Z" fill="#7d7c7c" stroke="none" pointer-events="all"/&gt;&lt;path d="M 560 66.66 L 560 68.13 C 560 69.2 559.19 70 558.19 70 L 536.89 70 C 535.63 70 535 69 535 68.21 L 535 66.72 C 535 67.61 535.72 68.47 536.8 68.47 L 558.13 68.47 C 559.07 68.47 560 67.75 560 66.66 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="all"/&gt;&lt;rect x="535" y="35.79" width="0" height="0" fill="none" stroke="rgb(0, 0, 0)" pointer-events="all"/&gt;&lt;path d="M 556.07 42.6 L 538.87 42.6 L 538.87 40.59 L 556.07 40.59 Z M 556.07 46.11 L 538.87 46.11 L 538.87 44.09 L 556.07 44.09 Z M 556.07 49.6 L 538.87 49.6 L 538.87 47.6 L 556.07 47.6 Z" fill="#ffffff" stroke="none" pointer-events="all"/&gt;&lt;rect x="369" y="31.89" width="42" height="42" fill="none" stroke="none" pointer-events="all"/&gt;&lt;path d="M 385.81 63.07 L 385.69 60.88 C 385.69 59.99 384.95 59.35 384.22 59.13 C 383.91 59.13 383.61 59.01 383.39 58.81 C 383.27 58.81 383.27 58.68 383.18 58.68 C 383.09 58.68 383.09 58.58 382.96 58.58 C 383.48 58.24 383.91 57.48 383.91 56.71 C 384 56.39 384 55.73 384 55.41 C 383.91 55.09 383.82 55.19 383.82 55.09 C 383.61 55.09 383.48 54.86 383.27 54.86 C 382.96 54.65 382.54 54.74 382.23 54.86 C 382.14 54.86 381.93 54.86 381.8 54.86 C 381.71 54.86 381.59 54.86 381.5 54.86 C 381.4 54.86 381.07 55.09 380.98 55.41 C 380.86 55.85 381.07 56.71 381.07 56.84 C 381.07 57.6 381.4 58.24 381.93 58.58 C 381.93 58.58 381.8 58.58 381.8 58.68 C 381.71 58.68 381.59 58.68 381.59 58.81 C 381.4 59.01 381.07 59.13 380.77 59.13 C 380.03 59.45 379.39 59.99 379.3 60.88 C 379.3 60.88 379.3 60.88 379.08 63.07 C 379.08 63.07 380.55 63.39 382.54 63.39 C 384.34 63.39 385.81 63.07 385.81 63.07 Z M 400.93 63.07 L 400.81 60.88 C 400.81 59.99 400.29 59.35 399.68 59.13 C 399.46 59.13 399.35 59.01 399.16 58.81 C 399.04 58.81 399.04 58.68 398.94 58.68 C 398.94 58.68 398.94 58.58 398.94 58.58 C 399.25 58.24 399.56 57.48 399.56 56.71 C 399.56 56.39 399.56 55.73 399.56 55.41 C 399.56 55.09 399.46 55.19 399.46 55.09 C 399.35 55.09 399.25 54.86 399.04 54.86 C 398.82 54.65 398.39 54.74 398.3 54.86 C 398.09 54.86 398.09 54.86 397.88 54.86 C 397.88 54.86 397.78 54.86 397.78 54.86 C 397.57 54.86 397.47 55.09 397.36 55.41 C 397.26 55.85 397.36 56.71 397.36 56.84 C 397.47 57.6 397.66 58.24 398 58.58 C 398 58.58 398 58.58 397.88 58.68 C 397.88 58.68 397.88 58.68 397.78 58.81 C 397.57 59.01 397.47 59.13 397.26 59.13 C 396.62 59.45 396.1 59.99 396.1 60.88 C 396.1 60.88 396.1 60.88 395.89 63.07 C 395.89 63.07 397.05 63.39 398.39 63.39 C 399.89 63.39 400.93 63.07 400.93 63.07 Z M 396.63 40.09 C 394.95 38.34 392.63 37.26 390.12 37.26 C 384.95 37.13 380.77 41.62 380.77 47.09 C 380.98 49.72 381.93 52.23 383.61 53.88 C 384.86 55.29 386.32 56.18 388 56.62 C 388.13 67.11 388.13 67.11 388.13 67.11 C 388 67.97 388.74 68.64 389.6 68.64 C 390.64 68.64 390.64 68.64 390.64 68.64 C 391.58 68.64 392.22 67.97 392.22 67.11 C 392.22 56.62 392.22 56.62 392.22 56.62 C 396.2 55.51 399.26 51.69 399.26 46.99 C 399.26 44.35 398.31 41.84 396.63 40.09 Z M 389.99 54.53 C 386.12 54.53 382.96 51.15 382.96 46.99 C 382.96 42.95 386.12 39.55 389.99 39.55 C 393.9 39.55 397.05 42.95 397.05 46.99 C 397.05 51.15 393.9 54.53 389.99 54.53 Z M 389.69 40.97 C 389.38 41.07 389.17 40.97 388.96 40.97 C 388.86 40.97 388.65 40.97 388.52 40.97 C 388.31 41.07 387.91 41.41 387.8 42.05 C 387.58 42.72 387.8 44.37 387.8 44.59 C 387.91 45.99 388.43 47.2 389.07 47.64 C 389.07 47.74 389.07 47.86 388.96 47.86 C 388.86 48.09 388.74 48.09 388.65 48.18 C 388.31 48.51 387.91 48.72 387.49 48.85 C 386.84 49.07 386.23 49.49 385.81 50.16 C 386.75 51.81 388.31 52.89 390.12 52.89 C 391.8 52.89 393.26 51.9 394.21 50.25 C 393.9 49.62 393.17 49.07 392.41 48.85 C 392.1 48.72 391.67 48.63 391.37 48.31 C 391.27 48.18 391.15 48.09 390.94 47.96 C 390.85 47.86 390.85 47.74 390.73 47.74 C 391.58 47.2 392.1 45.9 392.22 44.24 C 392.22 43.92 392.22 42.62 392.22 41.95 C 392.1 41.51 392.01 41.51 391.89 41.51 C 391.67 41.41 391.49 40.97 391.15 40.87 C 390.64 40.65 389.99 40.87 389.69 40.97 Z M 369 52.9 C 369 41.3 378.44 31.89 390 31.89 C 401.67 31.89 411 41.3 411 52.9 C 411 64.6 401.67 73.89 390 73.89 C 378.44 73.89 369 64.6 369 52.9 Z" fill="#28a8e0" stroke="none" pointer-events="all"/&gt;&lt;ellipse cx="390" cy="52.89" rx="20.58" ry="20.58" fill="#ffffff" stroke="none" pointer-events="all"/&gt;&lt;rect x="369" y="31.89" width="42" height="42" fill="none" stroke="none" pointer-events="all"/&gt;&lt;path d="M 385.81 63.07 L 385.69 60.88 C 385.69 59.99 384.95 59.35 384.22 59.13 C 383.91 59.13 383.61 59.01 383.39 58.81 C 383.27 58.81 383.27 58.68 383.18 58.68 C 383.09 58.68 383.09 58.58 382.96 58.58 C 383.48 58.24 383.91 57.48 383.91 56.71 C 384 56.39 384 55.73 384 55.41 C 383.91 55.09 383.82 55.19 383.82 55.09 C 383.61 55.09 383.48 54.86 383.27 54.86 C 382.96 54.65 382.54 54.74 382.23 54.86 C 382.14 54.86 381.93 54.86 381.8 54.86 C 381.71 54.86 381.59 54.86 381.5 54.86 C 381.4 54.86 381.07 55.09 380.98 55.41 C 380.86 55.85 381.07 56.71 381.07 56.84 C 381.07 57.6 381.4 58.24 381.93 58.58 C 381.93 58.58 381.8 58.58 381.8 58.68 C 381.71 58.68 381.59 58.68 381.59 58.81 C 381.4 59.01 381.07 59.13 380.77 59.13 C 380.03 59.45 379.39 59.99 379.3 60.88 C 379.3 60.88 379.3 60.88 379.08 63.07 C 379.08 63.07 380.55 63.39 382.54 63.39 C 384.34 63.39 385.81 63.07 385.81 63.07 Z M 400.93 63.07 L 400.81 60.88 C 400.81 59.99 400.29 59.35 399.68 59.13 C 399.46 59.13 399.35 59.01 399.16 58.81 C 399.04 58.81 399.04 58.68 398.94 58.68 C 398.94 58.68 398.94 58.58 398.94 58.58 C 399.25 58.24 399.56 57.48 399.56 56.71 C 399.56 56.39 399.56 55.73 399.56 55.41 C 399.56 55.09 399.46 55.19 399.46 55.09 C 399.35 55.09 399.25 54.86 399.04 54.86 C 398.82 54.65 398.39 54.74 398.3 54.86 C 398.09 54.86 398.09 54.86 397.88 54.86 C 397.88 54.86 397.78 54.86 397.78 54.86 C 397.57 54.86 397.47 55.09 397.36 55.41 C 397.26 55.85 397.36 56.71 397.36 56.84 C 397.47 57.6 397.66 58.24 398 58.58 C 398 58.58 398 58.58 397.88 58.68 C 397.88 58.68 397.88 58.68 397.78 58.81 C 397.57 59.01 397.47 59.13 397.26 59.13 C 396.62 59.45 396.1 59.99 396.1 60.88 C 396.1 60.88 396.1 60.88 395.89 63.07 C 395.89 63.07 397.05 63.39 398.39 63.39 C 399.89 63.39 400.93 63.07 400.93 63.07 Z M 396.63 40.09 C 394.95 38.34 392.63 37.26 390.12 37.26 C 384.95 37.13 380.77 41.62 380.77 47.09 C 380.98 49.72 381.93 52.23 383.61 53.88 C 384.86 55.29 386.32 56.18 388 56.62 C 388.13 67.11 388.13 67.11 388.13 67.11 C 388 67.97 388.74 68.64 389.6 68.64 C 390.64 68.64 390.64 68.64 390.64 68.64 C 391.58 68.64 392.22 67.97 392.22 67.11 C 392.22 56.62 392.22 56.62 392.22 56.62 C 396.2 55.51 399.26 51.69 399.26 46.99 C 399.26 44.35 398.31 41.84 396.63 40.09 Z M 389.99 54.53 C 386.12 54.53 382.96 51.15 382.96 46.99 C 382.96 42.95 386.12 39.55 389.99 39.55 C 393.9 39.55 397.05 42.95 397.05 46.99 C 397.05 51.15 393.9 54.53 389.99 54.53 Z M 389.69 40.97 C 389.38 41.07 389.17 40.97 388.96 40.97 C 388.86 40.97 388.65 40.97 388.52 40.97 C 388.31 41.07 387.91 41.41 387.8 42.05 C 387.58 42.72 387.8 44.37 387.8 44.59 C 387.91 45.99 388.43 47.2 389.07 47.64 C 389.07 47.74 389.07 47.86 388.96 47.86 C 388.86 48.09 388.74 48.09 388.65 48.18 C 388.31 48.51 387.91 48.72 387.49 48.85 C 386.84 49.07 386.23 49.49 385.81 50.16 C 386.75 51.81 388.31 52.89 390.12 52.89 C 391.8 52.89 393.26 51.9 394.21 50.25 C 393.9 49.62 393.17 49.07 392.41 48.85 C 392.1 48.72 391.67 48.63 391.37 48.31 C 391.27 48.18 391.15 48.09 390.94 47.96 C 390.85 47.86 390.85 47.74 390.73 47.74 C 391.58 47.2 392.1 45.9 392.22 44.24 C 392.22 43.92 392.22 42.62 392.22 41.95 C 392.1 41.51 392.01 41.51 391.89 41.51 C 391.67 41.41 391.49 40.97 391.15 40.87 C 390.64 40.65 389.99 40.87 389.69 40.97 Z M 369 52.9 C 369 41.3 378.44 31.89 390 31.89 C 401.67 31.89 411 41.3 411 52.9 C 411 64.6 401.67 73.89 390 73.89 C 378.44 73.89 369 64.6 369 52.9 Z" fill="#28a8e0" stroke="none" pointer-events="all"/&gt;&lt;path d="M 390 260 L 420 260 L 420 300 L 396.37 300" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 391.12 300 L 398.12 296.5 L 396.37 300 L 398.12 303.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 253px; margin-left: 420px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;5.Validation&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="420" y="256" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;5.Validation&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/&gt;&lt;a transform="translate(0,-5)" xlink:href="https://www.drawio.com/doc/faq/svg-export-text-problems" target="_blank" rel="noopener"&gt;&lt;text text-anchor="middle" font-size="10px" x="50%" y="100%"&gt;Text is not SVG &amp;#8211; cannot display&lt;/text&gt;&lt;/a&gt;&lt;/switch&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Here is the narrative from my own words:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;The user launches client application, which detects that user has not logged in, and redirect to log in page.&lt;/li&gt;&#10;&lt;li&gt;The client app sends an HTTP request for authorization code to the /authorize endpoint of Authorization server. This request consists of the following fields:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Response type: code, indicating it is requesting authorization code&lt;/li&gt;&#10;&lt;li&gt;Scope: openid, standard for oidc&lt;/li&gt;&#10;&lt;li&gt;RedirectURI: my.com/oidc-callback, Authorization Server will use this to call back with code.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;The authorization server redirects the user to a prompt for log-in&lt;/li&gt;&#10;&lt;li&gt;The user completes authentication and consent&lt;/li&gt;&#10;&lt;li&gt;The Authorization server validates user information within its identity provider&lt;/li&gt;&#10;&lt;li&gt;The Authorization server fires an HTTP request call-back at the Redirect URI (on the backend), with Authorization Code. &lt;/li&gt;&#10;&lt;li&gt;The client app issues an HTTP request for ID Token and Access Token to the /token endpoint of Authorization server. This request consists of:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Authorization Code (received from previous step)&lt;/li&gt;&#10;&lt;li&gt;Client ID&lt;/li&gt;&#10;&lt;li&gt;Client Secret&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;The Authorization server validates the information and process the request, and prepare the response with the following fields:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;ID Token: identifies the resource owner.&lt;/li&gt;&#10;&lt;li&gt;Access Token: identifies what the client app can access&lt;/li&gt;&#10;&lt;li&gt;Expiration&lt;/li&gt;&#10;&lt;li&gt;(Optional) Refresh Token&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;The client app receives the tokens above in the HTTP response from the /token endpoint&lt;/li&gt;&#10;&lt;li&gt;With the Tokens, the client app issues API requests to the resource server&lt;/li&gt;&#10;&lt;li&gt;The resource server independently validates the token&lt;/li&gt;&#10;&lt;li&gt;The resource server send API response back the the client app.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are also a few points of configurations. First, the Resource Server needs to trust the Authorization Server. The Authorization Server uses its private key to sign the JWT tokens and the Resource Server needs the public key to validates it. Second, the authorization server needs to know about the client app. We usually configure the Authorization Server upfront, to generate the client ID and secret. The Client app will keep them as part of its configuration. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It is important to note that, when I use the term client app (OIDC calls it client), the word &amp;#8220;client&amp;#8221; is relative to the Authorization server. The client app itself consists of both frontend (browser) and backend (aka relying party). In this flow, the authorization code is not exposed to browser.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Other OIDC Flows&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now we can discuss some flows for other architectures.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Authorization Code flow has a close variation with the use of PKCE (Proof Key for Code Exchange). For native apps, &lt;a href="https://blog.postman.com/pkce-oauth-how-to/"&gt;postman&lt;/a&gt; and Okta recommend Authorization Code flow with PKCE. When client app first requests for authorization code, it also includes a challenge. After the callback, when it sends the authorization code back to authorization server in exchange for tokens, the request now adds a verifier. This way, even if the authorization code may not be securely saved, the authorization server can ensure it is the same client app that requests authorization code and that requests tokens.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;An SPA or JavaScript app does not have a way to a. store Authorization code and b. listen on a call-back URI. As a result, it makes sense for the SPA to just fetch the Tokens directly. This make the implicit flow. The spec doc refers to it as simplified authorization code flow. The grant type is &amp;#8220;implicit&amp;#8221; because there is no intermediate credentials issued. In this flow, the Authorization server does not authenticate its client. The tokens may be exposed to resource owner or other applications with access to resource owner&amp;#8217;s user-agent. This flow improve the responsiveness but we should be wary of the security implications.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="631px" viewBox="-0.5 -0.5 631 421" style="max-width:100%;max-height:421px;"&gt;&lt;defs&gt;&lt;style type="text/css"&gt;@import url(https://fonts.googleapis.com/css?family=Architects+Daughter);&amp;#xa;@import url(https://fonts.googleapis.com/css?family=Architects+Daughter);&amp;#xa;&lt;/style&gt;&lt;/defs&gt;&lt;g&gt;&lt;rect x="0" y="0" width="630" height="420" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 628px; height: 1px; padding-top: 7px; margin-left: 1px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;"&gt;OIDC Implicit Flow&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="315" y="19" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold"&gt;OIDC Implicit Flow&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="20" y="78.14" width="100" height="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 98px; margin-left: 21px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Resource Owner&lt;br /&gt;(User)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="70" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Resource Owner&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="180" y="78.14" width="100" height="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 98px; margin-left: 181px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;SPA in Browser&lt;br /&gt;(frontend)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="230" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;SPA in Browser&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="340" y="78.14" width="100" height="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 98px; margin-left: 341px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Authorization Server&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="390" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Authorization Se&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 70 410 L 70 118.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/&gt;&lt;path d="M 230 410 L 230 118.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/&gt;&lt;path d="M 390 410 L 390 118.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/&gt;&lt;rect x="500" y="78.14" width="100" height="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 98px; margin-left: 501px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Resource Server&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="550" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Resource Server&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 550 410 L 550 118.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/&gt;&lt;path d="M 70 158.14 L 223.63 158.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 228.88 158.14 L 221.88 161.64 L 223.63 158.14 L 221.88 154.64 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 149px; margin-left: 151px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;1. launch SPA&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="151" y="152" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;1. launch SPA&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 230 178.14 L 383.63 178.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 388.88 178.14 L 381.88 181.64 L 383.63 178.14 L 381.88 174.64 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 171px; margin-left: 312px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;2. Token request to /authorize&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="312" y="174" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;2. Token request to /authorize&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 390 218.14 L 76.37 218.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 71.12 218.14 L 78.12 214.64 L 76.37 218.14 L 78.12 221.64 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 211px; margin-left: 180px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;3. 302 redirect to prompt user to log in&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="180" y="214" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;3. 302 redirect to prompt user to log in&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 70 248.14 L 383.63 248.14" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 388.88 248.14 L 381.88 251.64 L 383.63 248.14 L 381.88 244.64 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 239px; margin-left: 160px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;4. Authenticate and consent&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="160" y="242" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;4. Authenticate and consent&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 391.25 260 L 421.25 260 L 421.25 300 L 397.62 300" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 392.37 300 L 399.37 296.5 L 397.62 300 L 399.37 303.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 281px; margin-left: 462px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;5.Validation&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="462" y="284" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;5.Validation&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 390 320 L 236.37 320" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 231.12 320 L 238.12 316.5 L 236.37 320 L 238.12 323.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 303px; margin-left: 311px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;6. Response (HTTP) with &lt;br /&gt;IDToken and AccessToken&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="311" y="306" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;6. Response (HTTP) with&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 207.5 30 L 252.5 30 L 252.5 75 L 207.5 75 Z" fill="#ffffff" stroke="none" pointer-events="all"/&gt;&lt;path d="M 212 55.85 L 219.22 55.85 L 219.22 54.21 L 212 54.21 Z M 212 50.79 L 220.66 50.79 L 220.66 49.16 L 212 49.16 Z M 212 45.74 L 222.11 45.74 L 222.11 44.1 L 212 44.1 Z M 238.79 54.21 L 227.48 54.21 L 228.52 50.07 L 239.82 50.07 Z M 227.24 48.43 L 225.39 55.85 L 240.07 55.85 L 241.92 48.43 Z M 241.71 63.33 C 241.68 63.48 241.54 63.59 241.39 63.59 L 221.35 63.59 C 221.22 63.59 221.14 63.52 221.1 63.47 C 221.06 63.42 221.01 63.33 221.03 63.2 L 224.75 45.7 L 245.45 45.7 Z M 225.92 41.41 L 245.96 41.41 C 246.09 41.41 246.17 41.48 246.21 41.53 C 246.25 41.58 246.3 41.67 246.27 41.8 L 245.8 44.06 L 225.09 44.06 L 225.6 41.67 C 225.63 41.52 225.77 41.41 225.92 41.41 Z M 247.48 40.5 C 247.11 40.04 246.55 39.77 245.96 39.77 L 225.92 39.77 C 225 39.77 224.19 40.43 224 41.33 L 219.43 62.86 C 219.31 63.44 219.45 64.04 219.83 64.5 C 220.2 64.96 220.76 65.23 221.35 65.23 L 241.39 65.23 C 242.31 65.23 243.12 64.57 243.31 63.67 L 247.88 42.14 C 248 41.56 247.86 40.96 247.48 40.5 Z" fill="#232f3e" stroke="none" pointer-events="all"/&gt;&lt;path d="M 60 67 C 60 55 60 49 70 49 C 63.33 49 63.33 37 70 37 C 76.67 37 76.67 49 70 49 C 80 49 80 55 80 67 Z" fill="#f5f5f5" stroke="#666666" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;path d="M 230 350 L 543.63 350" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 548.88 350 L 541.88 353.5 L 543.63 350 L 541.88 346.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 341px; margin-left: 391px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;7. Issue API Request with Tokens&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="391" y="344" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;7. Issue API Request with Tokens&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 550 390 L 236.37 390" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 231.12 390 L 238.12 386.5 L 236.37 390 L 238.12 393.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 381px; margin-left: 349px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;11. Receive API Response&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="349" y="384" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;11. Receive API Response&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 550 360 L 580 360 L 580 380 L 556.37 380" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 551.12 380 L 558.12 376.5 L 556.37 380 L 558.12 383.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 352px; margin-left: 591px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;"&gt;8. Validation&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="591" y="355" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;8. Validation&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 536.89 70 C 535.63 70 535 69 535 68.21 L 535 37.58 C 535 36.62 535.74 35.79 536.76 35.79 L 558.22 35.79 C 558.93 35.79 560 36.37 560 37.63 L 560 68.13 C 560 69.2 559.19 70 558.19 70 Z" fill="#7d7c7c" stroke="none" pointer-events="all"/&gt;&lt;path d="M 560 66.66 L 560 68.13 C 560 69.2 559.19 70 558.19 70 L 536.89 70 C 535.63 70 535 69 535 68.21 L 535 66.72 C 535 67.61 535.72 68.47 536.8 68.47 L 558.13 68.47 C 559.07 68.47 560 67.75 560 66.66 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="all"/&gt;&lt;rect x="535" y="35.79" width="0" height="0" fill="none" stroke="rgb(0, 0, 0)" pointer-events="all"/&gt;&lt;path d="M 556.07 42.6 L 538.87 42.6 L 538.87 40.59 L 556.07 40.59 Z M 556.07 46.11 L 538.87 46.11 L 538.87 44.09 L 556.07 44.09 Z M 556.07 49.6 L 538.87 49.6 L 538.87 47.6 L 556.07 47.6 Z" fill="#ffffff" stroke="none" pointer-events="all"/&gt;&lt;rect x="369" y="31.89" width="42" height="42" fill="none" stroke="none" pointer-events="all"/&gt;&lt;path d="M 385.81 63.07 L 385.69 60.88 C 385.69 59.99 384.95 59.35 384.22 59.13 C 383.91 59.13 383.61 59.01 383.39 58.81 C 383.27 58.81 383.27 58.68 383.18 58.68 C 383.09 58.68 383.09 58.58 382.96 58.58 C 383.48 58.24 383.91 57.48 383.91 56.71 C 384 56.39 384 55.73 384 55.41 C 383.91 55.09 383.82 55.19 383.82 55.09 C 383.61 55.09 383.48 54.86 383.27 54.86 C 382.96 54.65 382.54 54.74 382.23 54.86 C 382.14 54.86 381.93 54.86 381.8 54.86 C 381.71 54.86 381.59 54.86 381.5 54.86 C 381.4 54.86 381.07 55.09 380.98 55.41 C 380.86 55.85 381.07 56.71 381.07 56.84 C 381.07 57.6 381.4 58.24 381.93 58.58 C 381.93 58.58 381.8 58.58 381.8 58.68 C 381.71 58.68 381.59 58.68 381.59 58.81 C 381.4 59.01 381.07 59.13 380.77 59.13 C 380.03 59.45 379.39 59.99 379.3 60.88 C 379.3 60.88 379.3 60.88 379.08 63.07 C 379.08 63.07 380.55 63.39 382.54 63.39 C 384.34 63.39 385.81 63.07 385.81 63.07 Z M 400.93 63.07 L 400.81 60.88 C 400.81 59.99 400.29 59.35 399.68 59.13 C 399.46 59.13 399.35 59.01 399.16 58.81 C 399.04 58.81 399.04 58.68 398.94 58.68 C 398.94 58.68 398.94 58.58 398.94 58.58 C 399.25 58.24 399.56 57.48 399.56 56.71 C 399.56 56.39 399.56 55.73 399.56 55.41 C 399.56 55.09 399.46 55.19 399.46 55.09 C 399.35 55.09 399.25 54.86 399.04 54.86 C 398.82 54.65 398.39 54.74 398.3 54.86 C 398.09 54.86 398.09 54.86 397.88 54.86 C 397.88 54.86 397.78 54.86 397.78 54.86 C 397.57 54.86 397.47 55.09 397.36 55.41 C 397.26 55.85 397.36 56.71 397.36 56.84 C 397.47 57.6 397.66 58.24 398 58.58 C 398 58.58 398 58.58 397.88 58.68 C 397.88 58.68 397.88 58.68 397.78 58.81 C 397.57 59.01 397.47 59.13 397.26 59.13 C 396.62 59.45 396.1 59.99 396.1 60.88 C 396.1 60.88 396.1 60.88 395.89 63.07 C 395.89 63.07 397.05 63.39 398.39 63.39 C 399.89 63.39 400.93 63.07 400.93 63.07 Z M 396.63 40.09 C 394.95 38.34 392.63 37.26 390.12 37.26 C 384.95 37.13 380.77 41.62 380.77 47.09 C 380.98 49.72 381.93 52.23 383.61 53.88 C 384.86 55.29 386.32 56.18 388 56.62 C 388.13 67.11 388.13 67.11 388.13 67.11 C 388 67.97 388.74 68.64 389.6 68.64 C 390.64 68.64 390.64 68.64 390.64 68.64 C 391.58 68.64 392.22 67.97 392.22 67.11 C 392.22 56.62 392.22 56.62 392.22 56.62 C 396.2 55.51 399.26 51.69 399.26 46.99 C 399.26 44.35 398.31 41.84 396.63 40.09 Z M 389.99 54.53 C 386.12 54.53 382.96 51.15 382.96 46.99 C 382.96 42.95 386.12 39.55 389.99 39.55 C 393.9 39.55 397.05 42.95 397.05 46.99 C 397.05 51.15 393.9 54.53 389.99 54.53 Z M 389.69 40.97 C 389.38 41.07 389.17 40.97 388.96 40.97 C 388.86 40.97 388.65 40.97 388.52 40.97 C 388.31 41.07 387.91 41.41 387.8 42.05 C 387.58 42.72 387.8 44.37 387.8 44.59 C 387.91 45.99 388.43 47.2 389.07 47.64 C 389.07 47.74 389.07 47.86 388.96 47.86 C 388.86 48.09 388.74 48.09 388.65 48.18 C 388.31 48.51 387.91 48.72 387.49 48.85 C 386.84 49.07 386.23 49.49 385.81 50.16 C 386.75 51.81 388.31 52.89 390.12 52.89 C 391.8 52.89 393.26 51.9 394.21 50.25 C 393.9 49.62 393.17 49.07 392.41 48.85 C 392.1 48.72 391.67 48.63 391.37 48.31 C 391.27 48.18 391.15 48.09 390.94 47.96 C 390.85 47.86 390.85 47.74 390.73 47.74 C 391.58 47.2 392.1 45.9 392.22 44.24 C 392.22 43.92 392.22 42.62 392.22 41.95 C 392.1 41.51 392.01 41.51 391.89 41.51 C 391.67 41.41 391.49 40.97 391.15 40.87 C 390.64 40.65 389.99 40.87 389.69 40.97 Z M 369 52.9 C 369 41.3 378.44 31.89 390 31.89 C 401.67 31.89 411 41.3 411 52.9 C 411 64.6 401.67 73.89 390 73.89 C 378.44 73.89 369 64.6 369 52.9 Z" fill="#28a8e0" stroke="none" pointer-events="all"/&gt;&lt;ellipse cx="390" cy="52.89" rx="20.58" ry="20.58" fill="#ffffff" stroke="none" pointer-events="all"/&gt;&lt;rect x="369" y="31.89" width="42" height="42" fill="none" stroke="none" pointer-events="all"/&gt;&lt;path d="M 385.81 63.07 L 385.69 60.88 C 385.69 59.99 384.95 59.35 384.22 59.13 C 383.91 59.13 383.61 59.01 383.39 58.81 C 383.27 58.81 383.27 58.68 383.18 58.68 C 383.09 58.68 383.09 58.58 382.96 58.58 C 383.48 58.24 383.91 57.48 383.91 56.71 C 384 56.39 384 55.73 384 55.41 C 383.91 55.09 383.82 55.19 383.82 55.09 C 383.61 55.09 383.48 54.86 383.27 54.86 C 382.96 54.65 382.54 54.74 382.23 54.86 C 382.14 54.86 381.93 54.86 381.8 54.86 C 381.71 54.86 381.59 54.86 381.5 54.86 C 381.4 54.86 381.07 55.09 380.98 55.41 C 380.86 55.85 381.07 56.71 381.07 56.84 C 381.07 57.6 381.4 58.24 381.93 58.58 C 381.93 58.58 381.8 58.58 381.8 58.68 C 381.71 58.68 381.59 58.68 381.59 58.81 C 381.4 59.01 381.07 59.13 380.77 59.13 C 380.03 59.45 379.39 59.99 379.3 60.88 C 379.3 60.88 379.3 60.88 379.08 63.07 C 379.08 63.07 380.55 63.39 382.54 63.39 C 384.34 63.39 385.81 63.07 385.81 63.07 Z M 400.93 63.07 L 400.81 60.88 C 400.81 59.99 400.29 59.35 399.68 59.13 C 399.46 59.13 399.35 59.01 399.16 58.81 C 399.04 58.81 399.04 58.68 398.94 58.68 C 398.94 58.68 398.94 58.58 398.94 58.58 C 399.25 58.24 399.56 57.48 399.56 56.71 C 399.56 56.39 399.56 55.73 399.56 55.41 C 399.56 55.09 399.46 55.19 399.46 55.09 C 399.35 55.09 399.25 54.86 399.04 54.86 C 398.82 54.65 398.39 54.74 398.3 54.86 C 398.09 54.86 398.09 54.86 397.88 54.86 C 397.88 54.86 397.78 54.86 397.78 54.86 C 397.57 54.86 397.47 55.09 397.36 55.41 C 397.26 55.85 397.36 56.71 397.36 56.84 C 397.47 57.6 397.66 58.24 398 58.58 C 398 58.58 398 58.58 397.88 58.68 C 397.88 58.68 397.88 58.68 397.78 58.81 C 397.57 59.01 397.47 59.13 397.26 59.13 C 396.62 59.45 396.1 59.99 396.1 60.88 C 396.1 60.88 396.1 60.88 395.89 63.07 C 395.89 63.07 397.05 63.39 398.39 63.39 C 399.89 63.39 400.93 63.07 400.93 63.07 Z M 396.63 40.09 C 394.95 38.34 392.63 37.26 390.12 37.26 C 384.95 37.13 380.77 41.62 380.77 47.09 C 380.98 49.72 381.93 52.23 383.61 53.88 C 384.86 55.29 386.32 56.18 388 56.62 C 388.13 67.11 388.13 67.11 388.13 67.11 C 388 67.97 388.74 68.64 389.6 68.64 C 390.64 68.64 390.64 68.64 390.64 68.64 C 391.58 68.64 392.22 67.97 392.22 67.11 C 392.22 56.62 392.22 56.62 392.22 56.62 C 396.2 55.51 399.26 51.69 399.26 46.99 C 399.26 44.35 398.31 41.84 396.63 40.09 Z M 389.99 54.53 C 386.12 54.53 382.96 51.15 382.96 46.99 C 382.96 42.95 386.12 39.55 389.99 39.55 C 393.9 39.55 397.05 42.95 397.05 46.99 C 397.05 51.15 393.9 54.53 389.99 54.53 Z M 389.69 40.97 C 389.38 41.07 389.17 40.97 388.96 40.97 C 388.86 40.97 388.65 40.97 388.52 40.97 C 388.31 41.07 387.91 41.41 387.8 42.05 C 387.58 42.72 387.8 44.37 387.8 44.59 C 387.91 45.99 388.43 47.2 389.07 47.64 C 389.07 47.74 389.07 47.86 388.96 47.86 C 388.86 48.09 388.74 48.09 388.65 48.18 C 388.31 48.51 387.91 48.72 387.49 48.85 C 386.84 49.07 386.23 49.49 385.81 50.16 C 386.75 51.81 388.31 52.89 390.12 52.89 C 391.8 52.89 393.26 51.9 394.21 50.25 C 393.9 49.62 393.17 49.07 392.41 48.85 C 392.1 48.72 391.67 48.63 391.37 48.31 C 391.27 48.18 391.15 48.09 390.94 47.96 C 390.85 47.86 390.85 47.74 390.73 47.74 C 391.58 47.2 392.1 45.9 392.22 44.24 C 392.22 43.92 392.22 42.62 392.22 41.95 C 392.1 41.51 392.01 41.51 391.89 41.51 C 391.67 41.41 391.49 40.97 391.15 40.87 C 390.64 40.65 389.99 40.87 389.69 40.97 Z M 369 52.9 C 369 41.3 378.44 31.89 390 31.89 C 401.67 31.89 411 41.3 411 52.9 C 411 64.6 401.67 73.89 390 73.89 C 378.44 73.89 369 64.6 369 52.9 Z" fill="#28a8e0" stroke="none" pointer-events="all"/&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/&gt;&lt;a transform="translate(0,-5)" xlink:href="https://www.drawio.com/doc/faq/svg-export-text-problems" target="_blank" rel="noopener"&gt;&lt;text text-anchor="middle" font-size="10px" x="50%" y="100%"&gt;Text is not SVG &amp;#8211; cannot display&lt;/text&gt;&lt;/a&gt;&lt;/switch&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As stated above, for SPAs (or in-browser JavaScript), we have to choice but the Implicit Flow because the client app is front-end only and we do not consider it able to securely store credentials. Client apps that can securely store client credentials may benefit from &lt;a href="https://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth"&gt;Hybrid Flow.&lt;/a&gt; In the hybrid flow, when the authorization server fires callback, the callback includes a single-use authorization code, along with ID token, access token, or both, depending on the provided response_type. Then the client app sends it back to authorization server, along with client credentials, in exchange for a second ID token and access token. The &lt;a href="https://openid.net/specs/openid-connect-core-1_0.html#HybridFlowSteps"&gt;specification&lt;/a&gt; has a good table that compares the three flows:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="255" src="https://www.digihunch.com/wp-content/uploads/2023/07/flow-table.webp" alt="" class="wp-image-12937" srcset="https://www.digihunch.com/wp-content/uploads/2023/07/flow-table.webp 1024w, https://www.digihunch.com/wp-content/uploads/2023/07/flow-table-300x75.webp 300w, https://www.digihunch.com/wp-content/uploads/2023/07/flow-table-768x191.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Apart from the three flows, there are also some &amp;#8220;unofficial&amp;#8221; OIDC flows, that are not discussed in the specification. For example, Auth0 adopted some original OAuth2.0 grants in conformance to OIDC, including &lt;a href="https://auth0.com/docs/authenticate/login/oidc-conformant-authentication/oidc-adoption-client-credentials-flow"&gt;Client Credentials Flow with OIDC&lt;/a&gt; and &lt;a href="https://auth0.com/docs/authenticate/login/oidc-conformant-authentication/oidc-adoption-rop-flow"&gt;Resource Owner Password Flow with OIDC&lt;/a&gt;. The client credentials flow is for machine-to-machine identity and is not concerned with user identity.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Identify the Flow&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A challenge that I faced is to make sense of the Authorization Code Flow in real life. I realized that the components (Client App, Authorization Server and Resource Server) in Authorization Code Flow are conceptual. In real life we do not always find a counterpart that perfectly match their features. When we try to introduce OIDC for authentication, we often need to build our own solution, with additional tools.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Take the client app for example. We would need one or several components in real life to perform the followings in order to qualify as a Client App in the sense of Authorization Code Flow, it needs to:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;know the authorization endpoint and construct the HTTP request for Authorization code;&lt;/li&gt;&#10;&lt;li&gt;stand up an HTTP service (relying party) to listen to call back, and parse the Authorization Code;&lt;/li&gt;&#10;&lt;li&gt;securely store Authorization code, and have access to client ID and client secret;&lt;/li&gt;&#10;&lt;li&gt;construct a request for tokens using client ID, secret and authorization Code received;&lt;/li&gt;&#10;&lt;li&gt;parse the tokens from the response from Token endpoint&lt;/li&gt;&#10;&lt;li&gt;to pass the tokens along&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When developers builds an application with OIDC integration capability, they&amp;#8217;d have to implement all these using the library of their programming language. In addition to application&amp;#8217;s own server, the OIDC module will need its own backend capable of doing all the activities above. The alternative option is to introduce a OIDC capable client proxy service.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As for the Resource Server, it needs to have a trust on the Authorization Server, so that it can cryptographically validate the tokens that the Authorization Server has issued using the well-known public key.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On the Authorization Server side, as we discussed. It needs to provision client ID and client secret that itself can later recognize when client app connects to it. It also needs to have both authorization endpoint and token endpoint. Often times, the authorization server contains identity store and we&amp;#8217;d like to call it the identity provider, but that is not always the case. A company may have a home grown identity store that does not support OIDC. In that case, to qualify as an OIDC Authorization server, they need a server proxy.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Open ID Connect Specifications&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Despite the different implementation by different vendors, we often need to resort to the official standard documentation. &lt;a href="https://openid.net/developers/specs/"&gt;This &lt;/a&gt;Open ID connect page lists all the specification if you expand &amp;#8220;OpenID Connect specification&amp;#8221; under Final Specifications. The most commonly used ones are:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;OpenID Connect Core specification [&lt;strong&gt;OpenID.Core.Errata2&lt;/strong&gt;], which covers the foundation and three login flows (Authorization Code, Implicit and Hybrid). This was developed early and the current version [OpenID.Core.Errata2] is from Dec 2023 but the two previous versions [OpenID.Core.Errata1] and [OpenID.Core.Final] had been around since 2014;&lt;/li&gt;&#10;&lt;li&gt;Open ID Connect Session Management [&lt;strong&gt;OpenID.Session&lt;/strong&gt;], another core document that stipulates how to manage sessions, finalized in Sept 2022;&lt;/li&gt;&#10;&lt;li&gt;Open ID Connect Discovery 1.0 [&lt;strong&gt;OpenID.Discovery&lt;/strong&gt;], which stipulates the hosting OIDC discovery document, finalized in Dec 2023;&lt;/li&gt;&#10;&lt;li&gt;Open ID RP-Initiated Logout [&lt;strong&gt;OpenID.RPInitiated&lt;/strong&gt;], one of the logout flow specification, drafted in 2020 and finalized in Sep 2022;&lt;/li&gt;&#10;&lt;li&gt;Open ID Front-Channel Logout [&lt;strong&gt;OpenID.FrontChannel&lt;/strong&gt;], one of the logout flow specification, drafted from March 2016 and finalized in Oct 2022;&lt;/li&gt;&#10;&lt;li&gt;Open ID Back-Channel Logout [&lt;strong&gt;OpenID.BackChannel&lt;/strong&gt;], one of the logout flow specification, drafted in 2016 and finalized in Sep 2022;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Do read the specification when you&amp;#8217;re configuring integration. It is worth noting that apart from the Core specification which has been finalized for a decade, most of the other specifications did not finalize until late 2022. Therefore, it is important for integrators to validate the compliance state of the components in the implementation.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;OIDC Proxy&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As a result, with regard to OIDC, there are two categories of proxies: OIDC client proxy and OIDC server proxy. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For example, in my previous &lt;a href="https://www.digihunch.com/2022/02/istio-external-authorization/"&gt;post&lt;/a&gt;, I explained how to configure external authorization via OIDC in Istio. Looking at the diagram, it uses OAuth2 proxy to integrate with GCP as the authorization server. In this use case, GCP is natively OIDC capable, the the Hello Word App isn&amp;#8217;t. Therefore, the OAuth2-proxy that we introduced is an OIDC client proxy. &lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="584" height="373" src="https://www.digihunch.com/wp-content/uploads/2023/07/oauthproxy.png" alt="" class="wp-image-9277" srcset="https://www.digihunch.com/wp-content/uploads/2023/07/oauthproxy.png 584w, https://www.digihunch.com/wp-content/uploads/2023/07/oauthproxy-300x192.png 300w" sizes="auto, (max-width: 584px) 100vw, 584px" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For a corporate with Active Directory, the identity store only supports LDAP protocol. In order to quality the identity store as an OIDC Authorization Server, we would need a server-side proxy such as the LDAP &lt;a href="https://dexidp.io/docs/connectors/"&gt;connector in Dex&lt;/a&gt;, with the Active Directory as authentication source. The diagram of dex is a good summary of its role:&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full is-resized"&gt;&lt;img loading="lazy" decoding="async" width="760" height="460" src="https://www.digihunch.com/wp-content/uploads/2023/07/dex.png" alt="" class="wp-image-9255" style="width:461px;height:279px" srcset="https://www.digihunch.com/wp-content/uploads/2023/07/dex.png 760w, https://www.digihunch.com/wp-content/uploads/2023/07/dex-300x182.png 300w" sizes="auto, (max-width: 760px) 100vw, 760px" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In some scenarios, we refer to this role as identity broker. Dex is an identity broker. Another important project to know is KeyCloak, which is sponsored by Red Hat and now a CNCF project. Although you can configure KeyCloak as an identity broker, it is much more than a broker. KeyCloadk is a full-fledged identity and access management solution on its own. It can act as the entire Authorization server. The diagram in &lt;a href="https://developers.redhat.com/blog/2019/12/11/keycloak-core-concepts-of-open-source-identity-and-access-management#"&gt;this&lt;/a&gt; blog post summarizes its features well. It is for teams that wants to build their home grown identity store. Think of KeyCloak as a self-managed open-source alternative to IAM solutions such as Okta or Auth0.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Amazon Cognito user pool plays a similar role. A user pool serves as an identity store to an app. The integration (no matter which flow and how Cognito calls them) is supposed to be &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cognito_user_pool_client#allowed_oauth_flows"&gt;OIDC&lt;/a&gt; compliant. However, as of date, the integration with Cognito user pool isn&amp;#8217;t. For example, the &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/logout-endpoint.html"&gt;logout endpoint&lt;/a&gt; requires client_id parameter where as the &lt;a href="https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout"&gt;RP initiated logout specification&lt;/a&gt; has it optional. On the other hand, it can &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-identity-federation.html"&gt;federate&lt;/a&gt; its own identity pool with a third party via standard protocol including &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/federation-endpoints.html"&gt;OIDC&lt;/a&gt;.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="782" height="201" src="https://www.digihunch.com/wp-content/uploads/2023/07/cognito.png" alt="" class="wp-image-9550" srcset="https://www.digihunch.com/wp-content/uploads/2023/07/cognito.png 782w, https://www.digihunch.com/wp-content/uploads/2023/07/cognito-300x77.png 300w, https://www.digihunch.com/wp-content/uploads/2023/07/cognito-768x197.png 768w" sizes="auto, (max-width: 782px) 100vw, 782px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In any use case where we need to bring OIDC integration, we need to start with the flow recommendation for each architecture, then we examine the existing component against the flow diagram. From there, we can identify the missing pieces and determine where and how we should configure the proxy.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The OIDC topic confuses me big time every time I need to configure identity store. With this post, I was hoping to elaborate on the Authorization Code Flow for OIDC. See OpenID &lt;a href="https://openid.net/certification/"&gt;certification&lt;/a&gt; for a list of providers. Further I discussed the two categories of proxies in the OIDC picture. Hopefully, when the OIDC topic comes back again, I will be able to quickly match which is which, and identify the missing piece to build a solution.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2023/06/kubernetes-paas-and-red-hat-openshift/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Kubernetes Platform as a Service and Red Hat OpenShift&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2023/07/authenticate-kube-apiserver-via-oidc/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Authentication to kube-apiserver via OIDC&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>EKS impression</title><link>https://www.digihunch.com/2022/12/eks-impression/</link><pubDate>Fri, 23 Dec 2022 18:18:19 -0400</pubDate><guid>https://www.digihunch.com/2022/12/eks-impression/</guid><description>&lt;img src="https://www.digihunch.com/wp-content/uploads/2025/04/eks-impression-feature.webp" alt="Featured image of post EKS impression" /&gt;&lt;p class="wp-block-paragraph"&gt;I&amp;#8217;ve worked on a few &lt;a href="https://www.digihunch.com/2021/12/aks-troubleshooting-lessons-learned/"&gt;AKS projects&lt;/a&gt; previously. Since I joined AWS I wanted to put aside some time to check out EKS (Elastic Kubernetes Service). Here in this post, I put down my first impression on EKS, and also share my Terraform template in &lt;a href="https://github.com/digihunch/cloudkube"&gt;cloudkube&lt;/a&gt; project to create an EKS cluster.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Similar to AKS, EKS exposes API endpoint and the control plane components are hidden from AWS users. When creating EKS cluster it does not create the underlying VPC and subnets. Therefore, you have create an existing VPC and at least two subnets ahead of time, and specify them during EKS creation. Bear in mind that there is a &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/network_reqs.html"&gt;list of requirement&lt;/a&gt; for the VPC and subnets.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the cluster, the CNI that EKS officially supports is Amazon VPC CNI plugin. It is available as an add-on. Similar to Azure CNI, each Pod gets its own IP address. In addition, EKS supports other &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/alternate-cni-plugins.html"&gt;compatible CNI plugins&lt;/a&gt; such as Calico, Cilium, Weave Net and Antrea.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-computing-nodes-in-eks"&gt;Computing Nodes in EKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are three modes to address computing capacity: self-managed nodes, EKS managed node groups and AWS Fargate. The documentation has a &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/eks-compute.html"&gt;comparison table&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With self-managed nodes, users create EC2 instances separately and then register them to the control plane. The instances must use the same IAM role and AMI. You can use Auto Scaling groups of &lt;a href="https://aws.amazon.com/bottlerocket/"&gt;Bottlerocket&lt;/a&gt; (AWS-sponsored purpose-built Linux distro for container host) nodes. The self-managed node option is mostly for AWS outpost customers who bring in their own computing capacity from data centre.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If you provision computing capacity from AWS, it makes sense to assign EKS managed node groups when creating EKS cluster. We can turn on &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/autoscaling.html"&gt;Cluster Autoscaler&lt;/a&gt;, a Kubernetes construct to manage the auto scaling of node groups. Sometimes we want to have more than one node groups. For example, to build a multi-architecture cluster, we need one node group with amd64 nodes and the other with arm64 nodes (e.g. instances with &lt;a href="https://aws.amazon.com/ec2/graviton/"&gt;Graviton&lt;/a&gt; processor). In general, arm-based CPU delivers better performance with less power consumption and the industry is slowly moving towards more arm-based CPU architecture.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Fargate is what I call managed computing service for EKS. With Fargate you do not need to tweak Cluster Autoscaler to self-manage computing capacity. The Fargate documentation has a long list of &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/fargate.html"&gt;considerations&lt;/a&gt;. For example, Pods must match a Fargate profile (&lt;a href="https://github.com/digihunch/real-quicK-cluster/blob/main/eks/cluster-fargate.yaml"&gt;here&lt;/a&gt;&amp;#8216;s an example) at the time that they&amp;#8217;re scheduled to run on Fargate. So we need to build Fargate profile and Pod labelling properly. Also, Fargate does not support DaemonSet. Another big consideration is that Fargate does not support non-VPC CNI. In my opinion these are pretty significant limitations. Many workloads (system-level or application-level) would need Daemonset (e.g. kube-proxy, some CNI or CSI drivers, &lt;a href="https://www.dynatrace.com/support/help/setup-and-configuration/setup-on-container-platforms/kubernetes/get-started-with-kubernetes-monitoring/set-up-k8s-monitoring-daemonset"&gt;Dynatrace&lt;/a&gt; monitoring). &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The pro of Fargate is the serverless computing model. The construct of a Fargate profile isn&amp;#8217;t complicated. You just specify subnets, namespace and labels. However, the downside is the long list of considerations. Some teams may consider these restrictions too much. The other overhead is the need to manage Fargate profile to ensure all Pods are scheduled somewhere. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To me, using Fargate alone impairs portability of workload. The good thing is that Fargate and Managed Node Group are not mutually exclusive on a cluster. In most cases, we can go partially serverless, and reap the benefits of both of them. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Node AutoScaling&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For workloads that don&amp;#8217;t have a matching Fargate profile, we have to figure out node autoscaling ourselves. I touched on Cluster Autoscaler in &amp;#8220;&lt;a href="https://www.digihunch.com/2022/03/autoscaling-in-kubernetes-from-metric-based-to-event-driven/"&gt;Autoscaling on Kubernetes Platform&lt;/a&gt;&amp;#8220;. CA works on AWS as well and is triggered upon a Pod coming to &lt;em&gt;unschedulable&lt;/em&gt; status in Scheduler. There is some limitations though. For example, CA interacts with Autoscaling Group (instead of EC2 instances directly). When it determines it&amp;#8217;s time to scale up, it bumps up the desired capacity by one at a time in the Autoscaling group. The configurations in Autoscaling group may also be at play and CA do not have direct control. For example, the &amp;#8220;&lt;a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-scaling-cooldowns.html"&gt;scaling cooldown&lt;/a&gt;&amp;#8220;. The pool of nodes is homogenous as per the pre-configured launch template and CA has no control. If a Pod requires a different type of node (e.g. ARM64 CPU, spot instance, etc), then we&amp;#8217;d first have to create a node group with the desired node type. Moreover, in the worst cases, one-at-a-time scale-up does not meet the increase of demand driven by Pod increases, causing nuances such as racing conditions. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Because the Cluster Autoscaler doesn&amp;#8217;t really deal with the nodes themselves, this kind of integration is clunky and slow. Nearly half of Kubernetes customers on AWS report that configuring cluster auto scaling using the Kubernetes Cluster Autoscaler is challenging and restrictive, according to &lt;a href="https://aws.amazon.com/blogs/aws/introducing-karpenter-an-open-source-high-performance-kubernetes-cluster-autoscaler/"&gt;this&lt;/a&gt; blog post. As a result, AWS launched an open-source cluster autoscaler project, &lt;a href="https://karpenter.sh/"&gt;Karpenter&lt;/a&gt;. Karpenter first only supported EKS but now the support includes other CSPs. For EKS, Karpenter directly interact with different types of EC2 instances.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Karpenter makes node scaling work in a more cloud-native manner. In the presence of unschedulable Pods, Karpenter &lt;span style="text-decoration: underline" class="underline"&gt;bypasses the Kubernetes scheduler&lt;/span&gt; and works directly with the Cloud provider, to launch the minimal compute resources needed to fit those Pods and immediately binds the Pods to the newly provisioned Nodes without waiting for scheduler. As Pods are removed or rescheduled to other nodes, Karpenter looks for opportunities to terminate under-utilized nodes. Karpender defines a CR called Provisioner to specify node provisioning configuration, such as instance size, zone, CPU architecture, etc. It is a manifest that describes a node group so the node scaler is aware of all the available node types. You can have multiple Provisioners for different needs, just like node groups. The Provisioner CR can also set TTL for empty Nodes, such that once a Node has no pods other than DaemonSet, Karpenter will terminate the Node on TTL expiry.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Karpenter&amp;#8217;s idea is similar to the idea of AutoPilot cluster in GKE. The new EKS workshop has an &lt;a href="https://www.eksworkshop.com/docs/autoscaling/compute/karpenter/"&gt;section&lt;/a&gt; on how to set up CA and Karpenter in practice.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-identity-management-for-eks"&gt;Identity Management for EKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For IAM, we need to be concerned with three aspects. The management traffic to the cloud service, the management traffic for Kubernetes cluster and business traffic. &lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-white-background-color has-background has-fixed-layout"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Traffic type&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;AWS&lt;/th&gt;&lt;th&gt;Azure&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;I. Cloud Service Endpoint (Management Traffic for Cloud Service)&lt;/td&gt;&lt;td&gt;AWS IAM identity&lt;/td&gt;&lt;td&gt;Azure RBAC&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;II. Kubernetes API (Management Traffic for K8s Cluster)&lt;/td&gt;&lt;td&gt;IAM mapping or OIDC&lt;/td&gt;&lt;td&gt;Azure RBAC (implementation of OIDC)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;III. Business traffic&lt;/td&gt;&lt;td&gt;Up to Kubernetes Ingress&lt;/td&gt;&lt;td&gt;Up to Kubernetes Ingress&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For business traffic (type III), it is all up to the Ingress. I&amp;#8217;ve written another article on &lt;a href="https://medium.com/slalom-build/managing-ingress-traffic-on-kubernetes-platforms-ebd537cdfb46"&gt;managing ingress traffic on Kubernetes platforms&lt;/a&gt;. We interact with cloud service endpoint (type II) with either AWS CLI or Terraform, to create any object, including resources needed for a cluster. This is generally how we work with cloud service, not specific to Kubernetes. Usually the IAM identity assumes another IAM role, which empowers it with a lot of permissions.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For access to Kubernetes API (type III), EKS supports OIDC and IAM mapping. AWS documentation refers to this as &amp;#8220;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/cluster-auth.html"&gt;Cluster Authentication&lt;/a&gt;&amp;#8220;. There is one special scenario where your identity for type II access inherits your identity for type I access. As the &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html"&gt;document&lt;/a&gt; puts:&lt;/p&gt;&#10;&lt;blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When you create an Amazon EKS cluster, the AWS Identity and Access Management (IAM) entity user or role, such as a&amp;nbsp;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers.html"&gt;federated user&lt;/a&gt;&amp;nbsp;that creates the cluster, is automatically granted&amp;nbsp;&lt;code&gt;system:masters&lt;/code&gt;&amp;nbsp;permissions in the cluster&amp;#8217;s role-based access control (RBAC) configuration in the Amazon EKS control plane. This IAM entity doesn&amp;#8217;t appear in any visible configuration, so make sure to keep track of which IAM entity originally created the cluster.&amp;nbsp;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This special scenario (I call it the &amp;#8220;&lt;strong&gt;implicit master&lt;/strong&gt; &lt;strong&gt;user&lt;/strong&gt;&amp;#8220;) allows us to perform critical activities on the cluster, such as creating IAM mapping, or OIDC configuration. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The above addressed how AWS resource access Kubernetes resource. On the other hand, to address how a Kubernetes resource access AWS resources, we need IRSA (&lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-on-EKS-DevelopmentGuide/setting-up-enable-IAM.html"&gt;IAM Roles for Service Account&lt;/a&gt;). We have a service account in Kubernetes and map it to an IAM role.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;AppMesh&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://aws.amazon.com/blogs/compute/introducing-aws-app-mesh-service-mesh-for-microservices-on-aws/"&gt;AppMesh&lt;/a&gt; is AWS&amp;#8217; Envoy based service-mesh offering supporting Kubernetes cluster, ECS service and even EC2 instance. AppMesh&amp;#8217;s control plane is a managed AWS service, with a &lt;a href="https://aws.github.io/aws-app-mesh-controller-for-k8s/"&gt;controller&lt;/a&gt; running on the Kubernetes cluster. To install AppMesh on the cluster:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;On the EKS cluster, install AppMesh Controller using Helm&lt;/li&gt;&#10;&lt;li&gt;Associate the cluster with IAM OIDC provider&lt;/li&gt;&#10;&lt;li&gt;Create an IAM role for the appmesh-controller service account&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;After these steps, you can create a mesh using CloudFormation, Terraform, etc. The data plane (Envoy proxy) can run on Kubernetes (as sidecar). Traffic between control plane and data plane can go through private link (Interface VPC &lt;a href="https://docs.aws.amazon.com/app-mesh/latest/userguide/infrastructure-security.html"&gt;endpoint&lt;/a&gt;) for added security. Like Istio, AppMesh enables mTLS. For observability, you can export Envoy metrics with Prometheus. Coupled with XRay, AppMesh also supports distributed tracing.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="832" height="354" src="https://www.digihunch.com/wp-content/uploads/2022/12/appmesh-virtualgateway.webp" alt="" class="wp-image-12883" srcset="https://www.digihunch.com/wp-content/uploads/2022/12/appmesh-virtualgateway.webp 832w, https://www.digihunch.com/wp-content/uploads/2022/12/appmesh-virtualgateway-300x128.webp 300w, https://www.digihunch.com/wp-content/uploads/2022/12/appmesh-virtualgateway-768x327.webp 768w" sizes="auto, (max-width: 832px) 100vw, 832px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AppMesh uses a different set of CRDs than Istio. Key CRDs are:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;Mesh&lt;/strong&gt;: represents an entire service mesh. At mesh level you can configure Egress filter (to allow or deny external traffic) and set IP version (v4 vs v6)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;VirtualGateway&lt;/strong&gt;: a CRD that represents an &lt;a href="https://aws.amazon.com/blogs/containers/introducing-ingress-support-in-aws-app-mesh/"&gt;Ingress&lt;/a&gt; in to the Mesh. A virtual gateway allows resources that are outside of your mesh to communicate to resources that are inside of your mesh. A virtual gateway references Envoy proxy deployment by podSelector. It references GatewayRoutes by namespaceSelector, and optionally gatewayRouteSelector. You also specify listeners in the manifest to reference Envoy proxy Service (LoadBalancer Type).&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;GatewayRoute&lt;/strong&gt;: A gateway route is attached to a virtual gateway and routes traffic to an existing virtual service. If a route matches a request, it can distribute traffic to a target virtual service. In the manifest, you specify a list of httpRoute, each with matching condition and action. In the action section you can specify virtualService as target.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;VirtualService&lt;/strong&gt;: an abstraction of a real service provided by a virtual node directly or indirectly by means of a virtual router. Dependent services call your virtual service by its virtualServiceName, and those requests are routed to the VirtualNode or VirtualRouter that is specified as the provider for the VirtualService.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;VirtualRouter&lt;/strong&gt;: Virtual routers handle traffic for virtual services. In a virtual router manifest, you can define Route to direct incoming requests to virtual nodes as target.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;VirtualNode&lt;/strong&gt;: A virtual node acts as a logical pointer to a particular task group (i.e. ECS service, Kubernetes deployment). It represent a Service in the AppMesh. In the manifest, you reference Pods by podSelector, specify listeners for any inbound traffic that your virtual node expects, and specify serviceDiscovery for your task group.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You can configure those Custom Resources using YAML manifests (and check the &lt;a href="https://docs.aws.amazon.com/app-mesh/latest/APIReference/Welcome.html"&gt;API reference&lt;/a&gt; a lot). Alternatively, you can configure them from AWS CLI or AWS console. The console will help you visualize what can be configured. For further details on how these CRs play together, there is a &lt;a href="https://www.appmeshworkshop.com/"&gt;workshop&lt;/a&gt; for AppMesh.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;EKS cluster using Terraform&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Officially, there is an &lt;a href="https://github.com/aws-ia/terraform-aws-eks-blueprints"&gt;EKS blueprint&lt;/a&gt; project for provisioning EKS cluster in Terraform.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I also keep my own Terraform code in the AWS directory of &lt;a href="https://github.com/digihunch/cloudkube"&gt;cloudkube&lt;/a&gt; project. It works out to be a little more complex than my Terraform template to create Azure Kubernetes Cluster (Azure directory). Because I had to create Cognito resources with initial credential to allow users to connect to cluster without using the implicit master account.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Below is the diagram of the processes.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="776" height="496" src="https://www.digihunch.com/wp-content/uploads/2022/12/eks-tf-mod.webp" alt="" class="wp-image-12881" srcset="https://www.digihunch.com/wp-content/uploads/2022/12/eks-tf-mod.webp 776w, https://www.digihunch.com/wp-content/uploads/2022/12/eks-tf-mod-300x192.webp 300w, https://www.digihunch.com/wp-content/uploads/2022/12/eks-tf-mod-768x491.webp 768w" sizes="auto, (max-width: 776px) 100vw, 776px" /&gt;&lt;figcaption class="wp-element-caption"&gt;Create EKS cluster with Terraform module&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The template configures kubectl access on a Bastion host, which assumed the same role that our IAM user uses to create the Kubernetes cluster. Therefore, the IAM role is the master identity. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that the IAM user (power-user) has very powerful permissions. Usually it is ideal to assign lots of permission to IAM Roles (temporary credential) instead of IAM user (long-term credential). So the &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_terms-and-concepts.html"&gt;role chaining&lt;/a&gt; would look like:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;The IAM user that Terraform uses has no permission other than assuming a &amp;#8220;PowerUser&amp;#8221; role&lt;/li&gt;&#10;&lt;li&gt;The PowerUser role trusts the IAM user. It also has the permission to assume the &amp;#8220;EKS-Manager&amp;#8221; role&lt;/li&gt;&#10;&lt;li&gt;The EKS-Manager role trusts PowerUser&amp;#8217;s role session.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;However, the role chaining scenario above is not currently supported in &lt;a href="https://github.com/hashicorp/terraform-provider-aws/issues/22728"&gt;Terraform&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I use a Bastion host because the cluster endpoint is on private subnet. The bastion host is on a public subnet. However, if we do not like public subnet and public IP, we can place the bastion host on a private subnet, and use SSM system manager agent with &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/systems-manager-ssh-vpc-resources/"&gt;SSH tunnel plugin &lt;/a&gt;to have SSH access to private bastion host.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-summary"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I first came across &lt;a href="https://medium.com/@andreidascalu/the-awfulness-of-aws-eks-d7700c1eccdc"&gt;this&lt;/a&gt; article about EKS and its awfulness and then decided to check out EKS. I&amp;#8217;m not sure all points are still valid but it&amp;#8217;s generally real-life experiences. There are also many peripheral services, such as AMP (AWS Managed Prometheus), AMG (AWS Managed Grafana), ADOT (AWS Distro for Open Telemetry), AppMesh (Another &lt;a href="https://www.appmeshworkshop.com/introduction/appmesh_components/"&gt;Envoy-based Service Mesh&lt;/a&gt;, &lt;a href="https://vedcraft.com/architecture/aws-appmesh-vs-istio-comparison-of-service-mesh/"&gt;easier to manage than Istio&lt;/a&gt; but less Powerful), with a lot to explore.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2022/12/landing-zone-in-aws/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Landing Zone in AWS – An Introduction&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2023/01/optimize-cpu-and-memory-for-kubernetes-pods/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Optimize CPU and Memory for Kubernetes Pod&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>