<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Authress Engineering Blog</title>
    <description>The latest articles on DEV Community by Authress Engineering Blog (@authress).</description>
    <link>https://dev.to/authress</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2625%2F18c2bb45-3a91-4fc8-86a0-3006f2b6b93a.png</url>
      <title>DEV Community: Authress Engineering Blog</title>
      <link>https://dev.to/authress</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/authress"/>
    <language>en</language>
    <item>
      <title>The Risks of User Impersonation</title>
      <dc:creator>Warren Parad</dc:creator>
      <pubDate>Fri, 24 Jan 2025 17:58:49 +0000</pubDate>
      <link>https://dev.to/authress/the-risks-of-user-impersonation-58nf</link>
      <guid>https://dev.to/authress/the-risks-of-user-impersonation-58nf</guid>
      <description>&lt;h2&gt;
  
  
  What is user impersonation?
&lt;/h2&gt;

&lt;p&gt;User impersonation is anything that allows your systems to believe the current logged in user is someone else. With regards to JWTs and access tokens, this means that one user obtains a JWT that contains another user's &lt;code&gt;User ID&lt;/code&gt;. User impersonation or logging in as a customer can be used as a tool to help identify many issues from user authentication and onboarding to corrupted data in complex multi-service business logic flows.&lt;/p&gt;

&lt;p&gt;However, at first glance it should is obvious that there are major security implications with such an approach. Even if it isn't, this article will extensively review user impersonation and the security implications as well as offer alternative suggestions to achieve a similar outcome in a software system without compromising security.&lt;/p&gt;

&lt;h2&gt;
  
  
  The impersonation use cases
&lt;/h2&gt;

&lt;p&gt;No solution is relevant in a vacuum, so let's consider the concrete issues that you might actually have, and the reason you've arrived at this &lt;a href="https://authress.io/knowledge-base/academy/topics" rel="noopener noreferrer"&gt;Authress Academy&lt;/a&gt; article. If we were to jump straight into a solution, then we'll definitely end up sacrificing security or worse, our user's sensitive data in favor of suboptimal solutions.&lt;/p&gt;

&lt;p&gt;Possible use case user stories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One of your users reports that they are experiencing an issue with a screen in your application portal not showing the correct information. As a support engineer, you want to review the exact display in the application UI that your user sees, so that you can verify the UI is indeed broken and something is actually going wrong.&lt;/li&gt;
&lt;li&gt;Similar to above, can you know whether or not the display having an issue is a result of a problem with the UI itself or with the data that application UI is fetching, hence a service API issue.&lt;/li&gt;
&lt;li&gt;Sometimes it is a problem with a complex API server flow. A click in your application portal was expected to perform a data change, transformation, or API request to your backend services, but is may not have been sent with the appropriate data. As an product engineer, you would like to know that the correct request data is being sent in the request to my service API.&lt;/li&gt;
&lt;li&gt;As an system admin, multiple third party systems are interacting with each other and something™ isn't working, and because you are a great collaborator, even though it isn't your responsibility, you want to help out your customers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, this list isn't exhaustive, but already you can start see that while focusing on the concrete problems, user impersonation might be useful, but these don't actually require it to debug. The root causes often fall into at least one of these categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;This is a UI component display issue.&lt;/li&gt;
&lt;li&gt;An unexpected request is being sent or isn't sent to your service API from your application portal.&lt;/li&gt;
&lt;li&gt;The wrong data is being sent in the request from your application UI to your API.&lt;/li&gt;
&lt;li&gt;It is a &lt;code&gt;READ&lt;/code&gt; permissions data issue for the user.&lt;/li&gt;
&lt;li&gt;It is a &lt;code&gt;WRITE&lt;/code&gt; permissions data issue for the user.&lt;/li&gt;
&lt;li&gt;In is multi-system problem and not an access issue, and having a duplicated environment that exactly matches the current production was your goal to continue debugging.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note: out of these solutions, none of them even get close to needing user impersonation, they each have straightforward alternatives that are both secure and frequently simpler to implement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supported libraries
&lt;/h2&gt;

&lt;p&gt;Fundamentally, &lt;strong&gt;user impersonation&lt;/strong&gt; is insecure by design, we'll see why in a moment. There are much better ways to provide insight into your specific scenario that actually take security into account. But let's assume that we do implement user personation. Is there help available for us by utilizing support from our favorite overengineered solution?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ankane/pretender" rel="noopener noreferrer"&gt;Ruby - Rails pretender&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/django-hijack/django-hijack" rel="noopener noreferrer"&gt;Python - Django hijack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/express-user-impersonation" rel="noopener noreferrer"&gt;Nodejs - Express/Passport impersonate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insert your favorite monolithic HTTP Framework here&lt;/strong&gt; ➤ Deprecated Solution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What's interesting is that in doing the research to actually find existing implementations, 86% of the repos and links I found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No longer exist, and haven't existed for quite some time&lt;/li&gt;
&lt;li&gt;Were archived over 5 years ago&lt;/li&gt;
&lt;li&gt;Have less than 10 stars on GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if people are trying to make this happen, the tools don't even exist to ensure that we are doing it correctly and safely. The results of this search, tell us something. Even more surprisingly is that most of the Auth SaaS solutions don't offer this either. As it turns out, either no one really cares that much or it is next to impossible to get it right such that no solution can exist. Well that can't be right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dangers of user impersonation
&lt;/h2&gt;

&lt;p&gt;Let's assume for a moment that the collective wisdom is correct, and no solutions exist because it is dangerous. What exactly are those dangers? To help convey these issues, say that we managed to get one of these legacy packages above actually working with our system, the first problem that we'll run into is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Who actually has access to perform this User Impersonation in the first place? Who are our admins?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1. Defining the admins
&lt;/h3&gt;

&lt;p&gt;Of course allowing everyone to impersonate one another basically means our authentication provides no value. We might as well let users enter whatever username they like on every post they make. Realistically, we want to restrict this list to those that it actually makes sense to have the ultimate &lt;code&gt;su&lt;/code&gt; privilege.&lt;/p&gt;

&lt;p&gt;Figuring out who the admins should be and maintaining access to that closely guarded endpoint that grants user impersonation is a common problem that even eludes the most sophisticated companies. The most notorious example of getting this wrong were the &lt;a href="https://en.wikipedia.org/wiki/2020_Twitter_account_hijacking" rel="noopener noreferrer"&gt;Twitter 2020 admin tools hack&lt;/a&gt; and the &lt;a href="https://msrc.microsoft.com/blog/2023/07/microsoft-mitigates-china-based-threat-actor-storm-0558-targeting-of-customer-email/" rel="noopener noreferrer"&gt;Microsoft Storm-0558&lt;/a&gt; breaches. Attackers were able to compromise admin-level account tools, and use them to steal and impersonate actual users. Historically, one of these companies had paid significant attention to their own internal security, were, if not the first, one of the first to introduce the notion of public social logins, and were no stranger to the issues at hand, and the other was Microsoft.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge 1: Maintaining both the admin list, and correctly securing the endpoint to allow impersonation in the first place.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The implementation
&lt;/h3&gt;

&lt;p&gt;The next issue regarding impersonation becomes transparent when we start to question how it can even work in practice. &lt;em&gt;In theory, practice is the same as theory, in practice it is not.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once admin is authorized to impersonate a user, what exactly is happening in our platform? Let's flash back to &lt;a href="https://authress.io/knowledge-base/academy/topics/implementating-user-login" rel="noopener noreferrer"&gt;Authentication&lt;/a&gt;. In order to secure your system, to ensure the right users have access to the right data at the right time, your users must use a session cookie or session token sent on every request for which your API can verify that user is logged in. This could be a completely opaque GUID that represents some data in your database (a reference token) or a more secure JWT that is stateless. In any case, your system identifies users via your &lt;a href="https://authress.io/knowledge-base/academy/topics/implementating-user-login" rel="noopener noreferrer"&gt;Authentication Strategy&lt;/a&gt;, and at the end of the day identification comes down to a single property in a single object somewhere. An example could be the JWT &lt;code&gt;subject claim&lt;/code&gt; property:&lt;/p&gt;

&lt;p&gt;User user_001 JWT access token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://login.authress.io&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="c1"&gt;// highlight&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sub&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// highlight&lt;/span&gt;

        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1685021390&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1685107790&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openid profile email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In OAuth/OpenID, the &lt;code&gt;sub&lt;/code&gt; claim in a JWT represent the &lt;strong&gt;User ID&lt;/strong&gt;. Thus this particular token represents a verified user with the identity &lt;code&gt;user_001&lt;/code&gt;. Anyone that holds this token is now has access to impersonate this user. Hopefully, you have some logging in place to identify when a user is being impersonated and who actually started the impersonation process. But how do we actually impersonate this user?&lt;/p&gt;

&lt;p&gt;Well of course, I need to convert a token that represents my admin user into a token that represents the user I want to impersonate. This would be an example of the token that I have right now.&lt;/p&gt;

&lt;p&gt;My admin user token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://login.authress.io&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="c1"&gt;// highlight&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sub&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;me_admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// highlight&lt;/span&gt;

        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1685021390&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1685107790&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openid profile email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since our system, in this scenario uses the &lt;code&gt;sub&lt;/code&gt; property to determine which user is accessing the system, I of course need a token that replaces the current value of the &lt;code&gt;sub&lt;/code&gt; which is &lt;code&gt;me_admin&lt;/code&gt; for me, to one that contains the &lt;code&gt;sub&lt;/code&gt; of &lt;code&gt;user_001&lt;/code&gt;. So when I impersonate the user, the result &lt;strong&gt;must be a token&lt;/strong&gt; that looks exactly like the user token:&lt;/p&gt;

&lt;p&gt;User token generated by the admin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://login.authress.io&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="c1"&gt;// highlight&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sub&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// highlight&lt;/span&gt;

        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1685021390&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1685107790&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openid profile email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some of http/auth frameworks have thought a whole two seconds longer than the rest and might have decided to add an additional property to indicate that the token was created through the process of impersonation by an admin instead of directly by the user:&lt;/p&gt;

&lt;p&gt;User token generated by the admin with magic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://login.authress.io&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sub&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user_001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="c1"&gt;// highlight&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;generated_by&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;me_admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// highlight&lt;/span&gt;

        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1685021390&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1685107790&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openid profile email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this might even seem like a good idea, however, in practice it creates a &lt;a href="https://authress.io/knowledge-base/articles/2025/01/03/bliss-security-framework" rel="noopener noreferrer"&gt;Pit of Failure&lt;/a&gt;. Enabling admin to create new tokens that contain the original user causes two distinct problems.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The first issue is that one admin user can impersonate another admin user. And that second admin user might be one that potentially has more access and is authorized for more sensitive information. This means that it isn't so straightforward to just add in impersonation and assume that everything will just work out. Our &lt;strong&gt;List of Admin&lt;/strong&gt;, no longer can just be a list of admin, it now must also contain some hierarchal order of who can impersonate whom. If you've been following along this looks a lot like what &lt;a href="https://authress.io/knowledge-base/docs/category/authorization" rel="noopener noreferrer"&gt;Authress Authorization&lt;/a&gt; provides. Of course you don't absolutely have to have that, but if you don't then you've sacrificed some security.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The second issue is that not every application you have might be interested in allowing users to be impersonated. In any mature system, and even most early software ventures, have some data that you are even less interested in exposing than rest. Sensitive by nature or Regulated data fits this picture. This could be Personal Identifiable Information (PII), Credit Cards (PCI-DSS), or really anything that has been regulated in your locality as a result of governing bodies. You might breach this through user impersonation if for instance your support engineer is in different &lt;a href="https://authress.io/knowledge-base/docs/authentication/user-authentication/selecting-data-residencies" rel="noopener noreferrer"&gt;Data Residency&lt;/a&gt; than the user is in. For example, when attempting to debug issues in a UI, almost never is the &lt;strong&gt;Date Of Birth (DOB)&lt;/strong&gt; of the user absolutely necessary to be shown on the screen. Sure it is relevant in some user use cases, but in most debugging scenarios it is not.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;If your authentication depends on the property &lt;code&gt;sub&lt;/code&gt; in the JWT, then an application cannot opt out of user impersonation. Since you are changing the &lt;code&gt;sub&lt;/code&gt; to be the impersonated user, every application will see the new &lt;code&gt;sub&lt;/code&gt; value, even if they do not want to support user impersonation. &lt;strong&gt;Strike 1.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;All applications are forced opted-in. If an application wants to opt-out then the second claim &lt;code&gt;generated_by&lt;/code&gt; or it's respective implementation is required. But then still, all applications are opted-in. That means when you design a new application you have to know that you might want to opt out admin from accessing user data in this application, "data is insecure by default, unless explicitly designed otherwise". This is the pit of failure, a pit of success would be opt-in, Data is secured by default, unless otherwise excluded. &lt;strong&gt;Strike 2.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;A quick call-out is worthwhile on how to secure data like a user's DOB. UIs don't need to know this information in most cases. The screens and activities where DOB is valuable, actually care that the user &lt;code&gt;isBornInJanuary&lt;/code&gt; or &lt;code&gt;isOlderThan18&lt;/code&gt;, and not the actual date of birth of the user. Unless of course this is the users DOB selection, in which case this component rarely needs to be validated by a support engineer, and if you believe that user impersonation is necessary to help validate the user DOB entry screen, this article isn't going to be of any help for you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. Secondary system data leakage
&lt;/h3&gt;

&lt;p&gt;Not only do we need to worry about vulnerabilities in our primary user applications, as well as leaking the data associated with them. Now we also need to worry about protecting these secondary systems used to impersonate users AND leaking the data associated with them as well. Internal systems, by their very design usually end having worse security measures in place because fewer people use them. Fewer users and lower volume means more hacks and less attention given to such an app. In practice, these applications are rarely changed, but frequently break, and most importantly have low priority when it comes to innovation and implementing necessary improvements. They don't end up in your OKR Objectives for this quarter and no one is getting promoted over them.&lt;/p&gt;

&lt;p&gt;We are so concerned that someone is abusing these tools that we ourselves leak user access tokens and data to logging systems. We log so zealously to ensure we have captured the usage of these tools, that we end up logging that which we should not. When we log that means we've probably also exported these logs to some third party reporting tools. It is a Catch-22, we know we need to log and report on actions taken as an admin when impersonating a user that log data that we would not normally be logging. The goal to prevent security issues creates a new attack surface.&lt;/p&gt;

&lt;p&gt;The result is that these systems will likely end up logging usage of user tokens. That's an introduction of a new attack surface, and due to the issues in priority with fixing, these systems are actually &lt;strong&gt;twice as likely to leak user data&lt;/strong&gt; compared to our primary user applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Corrupted audit trails
&lt;/h3&gt;

&lt;p&gt;Frequently we can a priori conclude that user impersonation is actually wrong. In the debugging scenarios, the last thing you want to do is gain access to modify the users' data. If you actually needed to modify a user's private data, or one of your customer's account information, you definitely want a dedicated system to handle that. This means, you actually don't want to the be the user, you don't want to impersonate the user, you just want to be the user with the explicit caveat of &lt;strong&gt;read only permissions&lt;/strong&gt;. You only want to see what they see, not actually be able to modify their data. Accidentally modifying user data is guaranteed to happen accidentally if the only way to to verify a user facing UX problem is to completely impersonate a user and get full write access to their account.&lt;/p&gt;

&lt;p&gt;Without thinking about, the following issues are associated with impersonating the user in this context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Audit trails incorrect say the user changed data when they did not. ➤ An admin impersonating the user did it.&lt;/li&gt;
&lt;li&gt;The user's sessions may start to include the one generated by the admin. ➤ As a user, it would be an understatement to say they would be concerned if they saw a session in a sensitive account modifying data from a location they are not in.&lt;/li&gt;
&lt;li&gt;Logging data in the applications is incorrectly recorded, or may not be recorded at all. ➤ You may be tempted to hide these admin interactions.&lt;/li&gt;
&lt;li&gt;And lastly, in every case, now we need to alter our systems to be not only aware of how to process the data due to impersonation, but how to log it.  ➤ Impersonation is a virus that starts to infect all of our systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The practical-ish solutions
&lt;/h2&gt;

&lt;p&gt;If generating a new token that contains the impersonated &lt;strong&gt;User ID&lt;/strong&gt; is so bad, there must be better solutions out there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution A: Additional token claim property
&lt;/h3&gt;

&lt;p&gt;What if we don't change the subject &lt;code&gt;sub&lt;/code&gt; claim, but instead add a new claim. That way, only those services that understand this claim, and actually want to use it would choose to use it. Services that don't know about it, keep using the unmodified &lt;code&gt;sub&lt;/code&gt; claim. Admins would still look like admins. Only services that care about a new &lt;code&gt;adminIsImpersonatingUserId&lt;/code&gt; claim property would know to use it and how to handle it. This would give you security by default, and only expose services to the danger that have already explicitly designed support for it. You would have to opt in, success finally!&lt;/p&gt;

&lt;p&gt;Theoretically this is great, and while it is a bit more secure than altering the subject, in practice, we start to write code that looks like this:&lt;/p&gt;

&lt;p&gt;Resolve User Identity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resolveUserIdentity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwtToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;adminIsImpersonatingUserId&lt;/span&gt;
          &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;jwtToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sub&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then that code ends up in a shared library which all our services implement. So while our intentions were good, the reinforcing system loops cause this to be no better than the alternatives. The reason is, we often find the need to optimize our usage across even a small number of services, some believe preventing code duplication is a bad thing. So the &lt;code&gt;resolveUserIdentity&lt;/code&gt; method leads us to the following pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We change our Auth solution to add the new claim to the JWT during impersonation.&lt;/li&gt;
&lt;li&gt;Only those services that need to care about this add support for it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At this point we are still 100% secure. But then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We update some shared libraries that support JWT verification and add the method &lt;code&gt;resolveUserIdentity&lt;/code&gt; to it.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;resolveUserIdentity&lt;/code&gt; replaces all the checks to consume the new claim.&lt;/li&gt;
&lt;li&gt;All existing services get updated to use this shared library, and are exposed to the dangers of impersonation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A new claim won't help us. This means that now we are back to the same problem, and arguably the &lt;strong&gt;situation is worse&lt;/strong&gt;. Instead of all the services in the platform trusting the standardize &lt;code&gt;sub&lt;/code&gt;, we now maintain a bespoke solution just for our system. This is especially important, the &lt;code&gt;sub&lt;/code&gt; claim is an &lt;code&gt;OAuth&lt;/code&gt; and &lt;code&gt;OpenID&lt;/code&gt; industry standard &lt;a href="https://datatracker.ietf.org/doc/html/rfc9068" rel="noopener noreferrer"&gt;RFC 9068&lt;/a&gt;, everyone in the industry is familiar with it. However, just for your system, there is now a new claim which just ends up being treated as the &lt;code&gt;sub&lt;/code&gt; canonical sub, but it is not standard, not self documenting, unexpected and unique. Complexity reduces security. &lt;strong&gt;Strike 3.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For more about the systemic issues with a JWT or session token based permission system, permission attenuation is discussed in depth in the &lt;a href="https://authress.io/knowledge-base/academy/topics/offline-attenuation" rel="noopener noreferrer"&gt;token scoping academy topic&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution B: DOM Recording
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;See earlier impersonation use cases.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If we flash back to the original user stories that drove us to implement user impersonation in the first place, we might start to see a pattern emerge. Most of the time the issue is that­ — ­something is wrong with the User Experience. The user is stuck in some way, the data isn't being displayed correctly, some component is broken.&lt;/p&gt;

&lt;p&gt;All of these are user facing issues, and issues facing the user purely in the UI. The source of the data, and the security therein has near-zero value to us in validating the user experience. Attempting to use &lt;strong&gt;expensive&lt;/strong&gt; full user impersonation instead of simple &lt;strong&gt;UI component&lt;/strong&gt; tests, is the exact same problem we see incorrectly implementing tests at the wrong level.&lt;/p&gt;

&lt;p&gt;Let's use the Testing Pyramid as an analogy. The canonical testing pyramid is this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ljalpcn93qnjyxhi4xc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ljalpcn93qnjyxhi4xc.png" alt="The Testing Pyramid" width="584" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At the bottom is our &lt;strong&gt;unit tests&lt;/strong&gt;, those tests are cheap and easy to write, find the most issues, and ensure our system is working without much effort.&lt;/li&gt;
&lt;li&gt;Then comes the &lt;strong&gt;service level tests&lt;/strong&gt;. Or in the case of UIs these are our screen tests. Multiple pieces of functionality and components are combined together in these tests. We don't want many of them, perhaps 10% max of all our tests test full screens or services. Most of the functionality of the service or screen is already validated in the unit tests — ­ie we know that our core functions, as well as buttons, slides, pickers, etc — all work correctly.&lt;/li&gt;
&lt;li&gt;Now comes the 1% &lt;strong&gt;integration or end-to-end tests&lt;/strong&gt;. You almost never want these, only the most critical flows of your application should be validated. When they report a failure, you have no idea what might have caused that particular failure, you just know there is a problem. In the case of an application like social media platform, The integration test you want is — making a new post. (Obviously there is no reason to test the login flow, since your auth provider has you already covered there!)&lt;/li&gt;
&lt;li&gt;At the top of the pyramid is &lt;strong&gt;manual exploratory testing&lt;/strong&gt;. That which cannot be automated, and most importantly needs the intelligence and creativity of a human to identify potential problems in your software application. This is the most expensive and you rarely have an interest in squandering this effort.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only difference between this and a support case is the context — &lt;strong&gt;the why&lt;/strong&gt;. The services, applications, business logic, and tools that we have at our disposal are all the same. We need to trust that our tests exist to validate the problems we could have. It is always a mistake to invest effort in the top of the pyramid when we lack the assets at the bottom. Likewise, our support pyramid is this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0y7k3oxbzl126nwv8wjx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0y7k3oxbzl126nwv8wjx.png" alt="The Support Pyramid" width="584" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At the bottom is &lt;strong&gt;application logs&lt;/strong&gt;. There is no sense in attempting to tackle any of the higher layers until you have sufficient application logs that exactly report incoming requests, outgoing responses, unexpected data scenarios, edge cases that aren't completely implemented, and systemic issues.&lt;/li&gt;
&lt;li&gt;Just above that is &lt;strong&gt;documentation&lt;/strong&gt;. This includes expected common flows, uncommon flows, and demos of the more complex to use aspects of our application. The biggest benefit of this documentation is that we can help out users. I want to repeat that it is more for us, than it is for our users. The pyramid exists to inform us what we should do, not how our users should operate.&lt;/li&gt;
&lt;li&gt;The next rung up are &lt;strong&gt;User recordings&lt;/strong&gt;. For users that are having issues, we have concrete recorded data for their flow. The flows would include anything relevant to the application, how they used it, what actions they took. All so we can actually see what happened in context for when there is a problem. No one wants to spend any time looking at recordings if they don't have to. It is also very difficult to identify the root cause of problems by reviewing a recording, but having them is indispensable to your support engineers when they need them, when a user has reported a issue. Solutions include &lt;a href="https://posthog.com/" rel="noopener noreferrer"&gt;PostHog&lt;/a&gt;, &lt;a href="https://www.fullstory.com/" rel="noopener noreferrer"&gt;FullStory&lt;/a&gt;, &lt;a href="https://sentry.io/welcome/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;. If you don't have these recordings, then the next best alternative (which is very far away) is getting a live screencast from the user. These are less useful, and more expensive to obtain. Worst of all, they can and &lt;a href="https://blog.1password.com/okta-incident/" rel="noopener noreferrer"&gt;have been used&lt;/a&gt; to breach sensitive systems.&lt;/li&gt;
&lt;li&gt;At the very top, is of course the thing you never want to have to do, and the topic of this article: &lt;strong&gt;Full user impersonation&lt;/strong&gt;. If everything else fails then at least we have user impersonation left in our toolkit. But this must only be used after we have significantly invested in all the other strategies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Assuming we have tackled the bottom two rungs of the table, the missing next component is the &lt;strong&gt;User recordings&lt;/strong&gt;. If you have those, which offer the ability to sanitize the data coming from users, then you've got the solution to 99% of all support cases. Having people jump in and impersonate users is just not necessary. And most importantly, if we look at who often needs to impersonate users, it isn't even the people who should have access to do so.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1sf805fd1q1cce8mql4d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1sf805fd1q1cce8mql4d.png" alt="Danger of impersontation" width="800" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Revisiting user impersonation
&lt;/h2&gt;

&lt;p&gt;Do you want to see the data or do you want to see what the user sees? In almost every case it is the former, seeing the data can be through an admin app. In the rare case that it is the later, we would need the exact permissions the user has, or some safer strict subset of them. So what's the right way to handle user impersonation in the case that we just can't live without it?&lt;/p&gt;

&lt;p&gt;The most important principle here is &lt;strong&gt;Secure by Default&lt;/strong&gt;. So far a blanket implementation is wrong, and there are too many &lt;a href="https://authress.io/knowledge-base/articles/2025/01/03/bliss-security-framework" rel="noopener noreferrer"&gt;pits of failure&lt;/a&gt; with the JWT, auth session, or reference token based approach.&lt;/p&gt;

&lt;p&gt;Looking at the support engineer use case, our needs would be satisfied if we were to explicitly hand out to the support staff just the permissions &lt;code&gt;read:logs&lt;/code&gt; to handle that specific support case. But it is quite something else to generate whole valid tokens that contain the subject different from the user requesting them and give those out to specific people. So as long as we have a system that allows us to provide our team members with explicit permissions to only the exact resources they need, then we have the capability to ensure we have a secure system that also solves all our use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Authress supports user impersonation
&lt;/h2&gt;

&lt;p&gt;I want to end this article with a discussion about how Authress solves the top of the pyramid user impersonation story. The caveat here being, that it is sometimes a trade-off some companies really want. They absolutely want to sacrifice security, increase vulnerabilities as well as their attack surface by introducing full user impersonation functionality. However from experience, very few of our customers have anything implemented in this space at all, and those that do have hooked their process into &lt;strong&gt;easy to grant permissions&lt;/strong&gt; through Authress, rather than &lt;strong&gt;full user identity impersonation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The real solution is to actually consider your support team persona when designing features. And this is what Authress optimizes for.&lt;/p&gt;

&lt;p&gt;The flow that we consider the most secure is explicitly and &lt;strong&gt;Temporarily grant your support user persona exactly one small additional set of permissions&lt;/strong&gt; relevant for the support case. When we do this we don't change how we determine identity, we only change the way we determine access. Authress supports this by allowing quick cloning of &lt;a href="https://authress.io/knowledge-base/docs/authorization/access-records" rel="noopener noreferrer"&gt;User Based Access Records&lt;/a&gt; which represent the permissions a user has. Since cloning is dynamic, a temporary access record can be created that only contains the &lt;code&gt;READ&lt;/code&gt; equivalent roles that the user has. And most cases, you can just directly assign your support engineers to a &lt;a href="https://authress.io/app/#/settings?focus=groups" rel="noopener noreferrer"&gt;Authress Permission Group&lt;/a&gt; with &lt;code&gt;READ ✶&lt;/code&gt; access, and never need to touch permissions again.&lt;/p&gt;

&lt;p&gt;Here is an example cloned access record, where the support engineer received just the &lt;strong&gt;Viewer&lt;/strong&gt; Role to all organizations so that documents and users could be &lt;code&gt;Read&lt;/code&gt; not &lt;code&gt;Updated&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv7no9dlsxhjp6ymi8uh0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv7no9dlsxhjp6ymi8uh0.png" alt="Access record example" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The firehouse recommendations
&lt;/h2&gt;

&lt;p&gt;In case you want to ignore the advise of this academy article, and instead of using Authress permissions to drive access control as recommended, I do want to include recommendations that will help reduce the impact of security and compliance issues related to user impersonation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not hide user impersonation, it will be tempting to obscure the usage of it from your customers. Instead make sure it is visible and clear for everyone especially your customers. I know you don't want them to know, but they should know, they may even need to know, especially if something goes wrong.&lt;/li&gt;
&lt;li&gt;Make sure all actions are recorded in an audit trail both by your admin who impersonated the user and the application user. &lt;strong&gt;Especially the admin&lt;/strong&gt;. There will definitely be questions related to the "last person that touched this" and of course "it was working before your team looked at it". You will need a way to be confident in your response to your customers when it wasn't an admin that touch it last.&lt;/li&gt;
&lt;li&gt;If you're operating in any high-security environment, FedRAMP, ITAR, or the like, always require customer user action before the support engineer has access to the account data. Some prominent cloud providers believe having an email with the user agreeing, is sufficient for this. I'm here to say — &lt;em&gt;is not sufficient&lt;/em&gt;. Because often people who can create support cases do not and should not have admin access to the customer account to view all the data. Someone without the customer admin role should be able to grant your support engineering staff access to sensitive data in the account. &lt;strong&gt;You need an admin to click a button.&lt;/strong&gt; This is usually done through a &lt;a href="https://authress.io/knowledge-base/docs/advanced/step-up-authorization#3-make-the-authorization-request" rel="noopener noreferrer"&gt;Step-Up Authorization Request&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Impersonation can be valuable in some environments however often completely useless in others. Especially in spaces with regulatory requirements, it's much better to diagnose issues from outside the impacted account, either through data replication or a permissions based approach.&lt;/li&gt;
&lt;li&gt;Ensure your impersonation logic is completely tested. There should be no better tested piece of functionality in your software system.&lt;/li&gt;
&lt;li&gt;Audit trails should always keep a "This was run-by User X" annotation on audit records, not just the user ID, but any additional information from the admin. Our recommendation is both the &lt;code&gt;Admin User ID&lt;/code&gt; and the &lt;code&gt;Support Ticket ID&lt;/code&gt;, on every log statement.&lt;/li&gt;
&lt;li&gt;Start with your customer expectations. What sort of transparency do they explicitly expect? Do not guess. Err on the side of overcommunicating, rather than under.&lt;/li&gt;
&lt;li&gt;Please revisit doing this in the first place if you don't have the capacity to have a dedicated team accountable for this functionality. Often this will involve your legal team when it doesn't go right.&lt;/li&gt;
&lt;li&gt;When (not if) credentials leak, who leaked those credentials? Was it your customer or was it through your admin application or by one of your support engineers. Always be able to tell where those credentials came from, so that you can respond to the compromise as effectively as possible.&lt;/li&gt;
&lt;li&gt;If you want to start anywhere, go back and invest in your admin/support tools so that they can expose the data that you need, rather than focusing on user impersonation. If those tools are insufficient check back at the Support Engineer Pyramid again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For help understanding this article or how you can implement a solution like this one in your services, feel free to reach out to the &lt;a href="https://authress.io/app/#/support" rel="noopener noreferrer"&gt;Authress development team&lt;/a&gt; or follow along in the &lt;a href="https://authress.io/knowledge-base" rel="noopener noreferrer"&gt;Authress documentation&lt;/a&gt; and join our community:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://authress.io/community" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Join the community&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>authorization</category>
      <category>identity</category>
      <category>security</category>
    </item>
    <item>
      <title>Are millions of accounts vulnerable due to Google's OAuth Flaw?</title>
      <dc:creator>Warren Parad</dc:creator>
      <pubDate>Wed, 15 Jan 2025 17:01:30 +0000</pubDate>
      <link>https://dev.to/authress/are-millions-of-accounts-vulnerable-due-to-googles-oauth-flaw-33f</link>
      <guid>https://dev.to/authress/are-millions-of-accounts-vulnerable-due-to-googles-oauth-flaw-33f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article is a rebuttal to &lt;a href="https://trufflesecurity.com/blog/millions-at-risk-due-to-google-s-oauth-flaw" rel="noopener noreferrer"&gt;Truffle Security's&lt;/a&gt; post on &lt;a href="https://trufflesecurity.com/blog/millions-at-risk-due-to-google-s-oauth-flaw" rel="noopener noreferrer"&gt;Millions of Accounts Vulnerable due to Google's OAuth Flaw&lt;/a&gt;. (&lt;em&gt;&lt;a href="https://authress.io/knowledge-base/assets/files/truffle-security-google-oauth-vulnerability-19b387e9c84f8ccfe621c0301c2a19d8.pdf" rel="noopener noreferrer"&gt;Alt link&lt;/a&gt;&lt;/em&gt;) Even more ridiculous might be that their post got picked up by no small number of news outlets that all should be ashamed of themselves, far too many to actually link in this post.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Are millions of accounts vulnerable due to Google's OAuth Flaw?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In a true &lt;a href="https://en.wikipedia.org/wiki/Betteridge%27s_law_of_headlines" rel="noopener noreferrer"&gt;Betteridge's law of headlines&lt;/a&gt; fashion, the answer is a resounding &lt;strong&gt;No&lt;/strong&gt;. Which explains why Google ignored this vulnerability in the first place:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8shcza2985fajtll95vh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8shcza2985fajtll95vh.png" alt="Google Workspace response" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The TL;DR of the source article claims that due to the nature of how Google OAuth works, &lt;strong&gt;"Millions of Americans' data and accounts remain vulnerable"&lt;/strong&gt;. It relies on the nature of Domain Ownership.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Claim
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Google’s OAuth login doesn’t protect against someone purchasing a failed startup’s domain and using it to re-create email accounts for former employees.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Domains are the root of trust* for many businesses. At Authress we rely on &lt;code&gt;authress.io&lt;/code&gt; to establish trust with our customers, just as at your business you rely on your domains for your customers. This is "Root of Trust" with an asterisk because in reality the root of trust lies with the domain authority, the domain registrar, and the issuer of your TLS certificates for HTTPS encryption. But that is outside of the scope of this article.&lt;/p&gt;

&lt;p&gt;The claim in the original article is that it is OAuth and specifically Google's OAuth that is at fault and nothing else. And that somehow domain ownership is linked to the exposure of customer data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Gaining access to your trusted domain is one way in which attackers attempt to circumvent your security strategy and compromise your users. If malicious attackers can utilize your domain to trick your users, then they can impersonate your business and steal their personal information, bank accounts, and credit card numbers. This is the basis for why phishing is popular today. As a matter of fact phishing is so popular because compromising a domain is incredibly hard, and is usually executed through a &lt;a href="https://www.cloudflare.com/learning/dns/dns-cache-poisoning/" rel="noopener noreferrer"&gt;DNS Poising attack&lt;/a&gt;. The strategy behind phishing is to purchase alternative domains that look and feel like the valid domain as the next best thing (&lt;a href="https://www.zscaler.com/blogs/security-research/phishing-typosquatting-and-brand-impersonation-trends-and-tactics" rel="noopener noreferrer"&gt;Typosquatting&lt;/a&gt;). These facsimiles exist for exactly that reason.&lt;/p&gt;

&lt;p&gt;Besides using separate domains attackers will often also attempt &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Subdomain_takeovers" rel="noopener noreferrer"&gt;Subdomain takeovers&lt;/a&gt; which is a mesh between domain compromise and using an alternative domain.&lt;/p&gt;

&lt;p&gt;However, in this case, attackers cleverly will attempt to use your existing corporate domain after you believe you are done with it. The expected flow involving Google Workspace's OAuth looks something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You buy a domain for your company, let's call it &lt;code&gt;yourcompany.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Sign up for an Employee Identity Solution (IdP) that provides OAuth, there are actually many solutions here, Google Workspace, &lt;a href="https://okta.com/" rel="noopener noreferrer"&gt;Okta&lt;/a&gt;, &lt;a href="https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id" rel="noopener noreferrer"&gt;Microsoft Entra ID&lt;/a&gt;, &lt;a href="https://www.pingidentity.com/en/resources/blog/post/okta-vs-ping-best-iam-digital-security.html" rel="noopener noreferrer"&gt;Ping Identity&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Then your employees use that identity solution to sign into to a third party product such as Stripe, AWS, PostHog, etc...&lt;/li&gt;
&lt;li&gt;Lastly you give critical data to that product, business sensitive information, like your pets' birthdays.&lt;/li&gt;
&lt;li&gt;That third party applications saves that data because they like data very much.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrev26ltr376vgehegjt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrev26ltr376vgehegjt.png" alt="Corporate Login Flow" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Identity
&lt;/h2&gt;

&lt;p&gt;When you log into your favorite third party application, there needs to be an identifier sent from the Employee Identity Solution to that third party. The Third Party trusts your chosen identity solution as well as that identifier. Here is an example token generated by Google Workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://accounts.google.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"210169484474386"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1736946817"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1736996817"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

      &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warren@yourcompany.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yourcompany.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Warren Parad"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"given_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Warren"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"family_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Parad"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The identifier in the token is the &lt;code&gt;sub&lt;/code&gt; claim with the value &lt;code&gt;210169484474386&lt;/code&gt;. This is my User ID (Note: this is not actually my user ID, feel free to do with it as you wish, but I made it up for the purposes of this post.)&lt;/p&gt;

&lt;p&gt;Your third party application uses this &lt;code&gt;sub&lt;/code&gt; property to uniquely identify you, and then authorize you to your company's sensitive cat photos.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vulnerability
&lt;/h2&gt;

&lt;p&gt;Now, imagine that you close your Google Workspace account, because your company goes bankrupt (This frequently happens because as much as we want to believe companies are successful through hard work, the &lt;a href="https://www.youtube.com/watch?v=3LopI4YeC4I" rel="noopener noreferrer"&gt;truth is that it is actually luck&lt;/a&gt;). Along with your Google Workspace Account will likely be your expired domain &lt;code&gt;yourcompany.com&lt;/code&gt;, unless you have some secret prayers that one day you will be able to sell it instead of expiring worthless. Let's assume that yourcompany.com domain is now available for anyone to purchase. By purchasing that domain, an attacker can create a new Google Workspace account, in hopes to gain access to those exact same third parties you had used for your business.&lt;/p&gt;

&lt;p&gt;This actually isn't even the first time something like this has been attempted, and frequently it works due to hard-coded solutions in many applications. In a cruel twist of fate, here is a great example of being able to compromise the attackers themselves because they had a used a application which relied on &lt;a href="https://labs.watchtowr.com/more-governments-backdoors-in-your-backdoors/" rel="noopener noreferrer"&gt;expired trusted malicious domains&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This actually doesn't happen with Google OAuth. When you close the google workspace account, the &lt;code&gt;User ID&lt;/code&gt; with the value &lt;code&gt;210169484474386&lt;/code&gt;, ceases to exist. This is what Google is confirming by closing the original bug report. An attacker recreating the Google Workspace account is unable to generate the same sub again. So that even if an attacker attempted to create a new Google Workspace from the expired and unclaimed domain &lt;code&gt;yourcompany.com&lt;/code&gt;, the sub would be different and your third party application would reject access.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the problem?
&lt;/h3&gt;

&lt;p&gt;The issue is some third party applications decided not to use the &lt;code&gt;sub&lt;/code&gt; claim. The author of the Truffle Security post suggests that this is due to some bug in the Google OAuth implementation, but the reality is OAuth has nothing to do with this problem. The failure to use the &lt;code&gt;sub&lt;/code&gt; claim stems from this shiny property in the identity token called &lt;code&gt;email&lt;/code&gt;. In the original token above you can see the users email there &lt;code&gt;warren@yourcompany.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A third party that utilizes this email address to uniquely identify users means that they are allowing malicious attackers who compromise employee identity providers through expired domains to take over your account. There are lots of reasons they do this, but primarily it is because they like the way the &lt;code&gt;@&lt;/code&gt; looks in their database.&lt;/p&gt;

&lt;p&gt;That means this is actually &lt;strong&gt;a vulnerability on the third party application side&lt;/strong&gt;. Any third party application that allows users to log in with just an email are inherently creating a vulnerability in their own platform and setting themselves up to expose their (ex-)users data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vulnerability review
&lt;/h2&gt;

&lt;p&gt;So, actually this has nothing to do with Google Workspace at all. And an attacker can actually use any email provider to perpetrate this attack:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Buy an expired domain and register your domain in a new email provider&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;li&gt;Profit&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Although in this case the &lt;code&gt;...&lt;/code&gt; is simply: &lt;strong&gt;Attempt a password reset or magic-link authentication for that third party application.&lt;/strong&gt; &lt;em&gt;In a similar attack a vulnerability was utilized by attackers through an &lt;a href="https://www.rescana.com/post/critical-zendesk-email-spoofing-vulnerability-cve-2024-49193-risks-and-mitigation-strategies" rel="noopener noreferrer"&gt;email support system&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The real vulnerability
&lt;/h3&gt;

&lt;p&gt;This shows us that OAuth and Google Workspace aren't actually the source of the issue here, it's the third party application. I've frequently condemned &lt;a href="https://authress.io/knowledge-base/articles/magic-links-passwordless-login" rel="noopener noreferrer"&gt;Magic-Link based Authentication&lt;/a&gt;, and while there are some areas where it unfortunately still provides value, it isn't worth it if you care about security. The fact that the email is provided by Google is just unfortunate. Emails are helpful for identify where to send messages to users who want emails, but it should never be used anywhere related to security.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Dismantling the solution
&lt;/h3&gt;

&lt;p&gt;The original article suggests that adding yet two more additional claims/properties to the User Identity Token, will solve the problem. One claim isn't good enough, let's have three!&lt;/p&gt;

&lt;p&gt;Given that the problem is that third party applications are ignoring the already existing &lt;code&gt;sub&lt;/code&gt; claim. I find it to be quite the naïve suggestion. No amount of additional claims will prevent third parties for incorrectly substituting in their beliefs where actual security is necessary. This is just an unfortunate truth. We see this every day and it is one of the reasons we built &lt;a href="https://authress.io" rel="noopener noreferrer"&gt;Authress&lt;/a&gt; in the first place. The defaults that exist in SDKs, frameworks, protocols, and standards, are just not enough for people to do the right thing, explicit investment had to be made in prevention of doing the wrong thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Third Party Application responsibility
&lt;/h3&gt;

&lt;p&gt;The last part of the problem is that the author in the original article claims&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What can Downstream Providers do to mitigate this? At the time of writing, there is no fix&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which just isn't true. Third party applications that allow email based authentication, must delete user data after account deactivation. Once you stop paying for a third party application, that data must be deleted and never exposed again unless you resume access and the third party verifies identity. I prefer taking guidance from the &lt;a href="https://pages.nist.gov/800-63-3-Implementation-Resources/63A/verification/" rel="noopener noreferrer"&gt;NIST 800-63A&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As a user you too, can do something to. If you have sensitive data, you could decide not to use any third party applications, unless of course you actually pay for it and ensure that you delete your account before your company stops using that application. If you give someone your data, they have it, assume the worst. We can and should put more responsibility onto these third party application services who are utilizing unsafe email addresses and often SMS numbers of authentication. As long as you treat email auth as a valid solution, everyone will forever be just as culpable as third parties who rely on it. Use &lt;a href="https://authress.io/knowledge-base/docs/authentication/user-authentication" rel="noopener noreferrer"&gt;OAuth and SAML&lt;/a&gt; for your &lt;a href="https://authress.io/knowledge-base/academy/topics/implementating-user-login" rel="noopener noreferrer"&gt;business authentication&lt;/a&gt; and make sure to provide sufficient &lt;a href="https://authress.io/knowledge-base/docs/authentication/user-authentication" rel="noopener noreferrer"&gt;secure options&lt;/a&gt; to the users of the products and services you build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consumer exposure
&lt;/h2&gt;

&lt;p&gt;The original article also seems to conflate risks to consumers directly. There is nothing about this vulnerability that directly affects consumers. Sure there are impacts to consumers regarding data privacy, but the vulnerability discussed in this article doesn't include them.&lt;/p&gt;

&lt;p&gt;That's because as a consumer when you use an application, that application stores data in their primary databases. When the company that manages that application fails, both their databases and their bank accounts are empty. You don't have to worry about that data. But you do have to worry about who they gave your data to. You have to worry about that irrespective of the company, or its state. Many companies out there have started to be investigating because of just that. This is the whole premise of the &lt;a href="https://en.wikipedia.org/wiki/Facebook%E2%80%93Cambridge_Analytica_data_scandal" rel="noopener noreferrer"&gt;Facebook's Cambridge Analytica scandal&lt;/a&gt;. Facebook gave user personal data to Cambridge Analytica when they should not have access to it. Facebook didn't even need to be bankrupt for there to be a problem.&lt;/p&gt;

&lt;p&gt;The core of the issue isn't the data you have given to the company, the problem is data the they have shared to others. But no amount of praying or technological solutions is going to fix that. The problems proposed in this article regarding the domain vulnerability in question are related to the data given to the third party applications secured with by the company's corporate domain. The data that is most vulnerable in these circumstances is the business-to-business relationships. Billing information, strategic partnerships, invoices, business strategies, these are at risk.&lt;/p&gt;

&lt;p&gt;For example, at Authress we use Stripe, sometimes. In stripe we have customer account information, including customer emails for sending invoices. If you are using Stripe or another payment provider, then chances are you too are storing some sort of customer data in Stripe. If your company goes bankrupt, and attacker uses the domain vulnerability to do a password reset on your Stripe account, they will now have access to your old company's customer invoice and email data. You probably don't care, but you should.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So I think we can say definitely, &lt;strong&gt;no there aren't millions of people at risk with this vulnerability&lt;/strong&gt;. Sure your data is at risk, it always had been at risk, it always will be at risk, but Google's OAuth implementation, while problematic, honestly doesn't change anything at all. You can continue to file your data deletion requests with your third party application providers when you don't think they are doing too well. But if they aren't doing that well, I sincerely doubt they are deleting your data, let alone deleting your data from their third party providers. I don't know what will become of the original published articles or Google's response, but I had felt strongly to first educate regarding the problem rather than lambast Google Workspace over their responses. The claim by the original author that &lt;strong&gt;millions of accounts vulnerable due to Google's OAuth Flaw&lt;/strong&gt; is just irresponsible.&lt;/p&gt;

&lt;p&gt;Curious about this and worth discussing more:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rhosys.ch/community" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Join the community&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>security</category>
      <category>startup</category>
      <category>oauth</category>
    </item>
    <item>
      <title>How does machine to machine authentication work?</title>
      <dc:creator>Warren Parad</dc:creator>
      <pubDate>Wed, 06 Dec 2023 11:37:34 +0000</pubDate>
      <link>https://dev.to/authress/how-does-machine-to-machine-authentication-work-569d</link>
      <guid>https://dev.to/authress/how-does-machine-to-machine-authentication-work-569d</guid>
      <description>&lt;p&gt;Machine to machine auth is how you ensure secure communication between individual services, and each service can authorize others to access protected resources.&lt;/p&gt;

&lt;p&gt;This article is part of the &lt;a href="https://authress.io/knowledge-base/academy/topics" rel="noopener noreferrer"&gt;Authress Academy&lt;/a&gt; and discusses how machine clients interact with each other in a secure way. Specifically it will dive into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A refresher on how JWTs work&lt;/li&gt;
&lt;li&gt;What a machine service is&lt;/li&gt;
&lt;li&gt;Token generation and how it differs from user JWT access tokens&lt;/li&gt;
&lt;li&gt;How generated JWTs are secured&lt;/li&gt;
&lt;li&gt;How to validate service client JWTs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Throughout the article we'll refer to our clients as &lt;code&gt;service clients&lt;/code&gt;. A service client is a machine or service entity that needs access to another service. Examples of service clients might be a service that interacts with the Slack or Google Workspace APIs. Or potentially one service in your platform that needs to communicate with another one--such as an &lt;code&gt;Orders Service&lt;/code&gt; that wants to fetch user related information from a &lt;code&gt;User Profile Service&lt;/code&gt;. These services will talk to each other. And they do that by creating and securing HTTP requests between each other. This is known as &lt;code&gt;machine-to-machine&lt;/code&gt; communication.&lt;/p&gt;

&lt;p&gt;Every client and user needs to identify who they are so that we can verify that identity and ensure that client is authorized to only be able to perform the actions they are allowed to perform.&lt;/p&gt;

&lt;p&gt;The working setup is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;End users that log into UIs&lt;/li&gt;
&lt;li&gt;Backend services that receive calls from these UIs&lt;/li&gt;
&lt;li&gt;These same services may also call each other&lt;/li&gt;
&lt;li&gt;A centralized authentication service such as &lt;a href="https://authress.io/knowledge-base/docs/category/introduction" rel="noopener noreferrer"&gt;Authress&lt;/a&gt; that enables verification of tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd6ifuszhoy7x6wnfi94i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd6ifuszhoy7x6wnfi94i.png" alt="Mahcine to machine integrations" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll remember that every token that is created in a platform must be verifiable. If these tokens can't be verified then any one can create any token, even one that has admin privileges. To guard against this, all tokens will be JWTs. JWTs have two important components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A user ID - known as the &lt;code&gt;sub&lt;/code&gt; claim&lt;/li&gt;
&lt;li&gt;The issuer - the source that created the token found in the &lt;code&gt;iss&lt;/code&gt; claim&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is an example JWT:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.authress.io/tokens"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These JWTs can be verified by using a standard library such as an &lt;a href="https://authress.io/knowledge-base/docs/SDKs" rel="noopener noreferrer"&gt;Authress SDK&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the case of your end users, you have a login portal that users are directed to in order to log in. Through the Auth provider, users are forwarded to a provider of their choice, such as Google, to log in. Once returning, your auth provider will verify the user identity and generate a JWT that represents that user.&lt;/p&gt;

&lt;p&gt;However, in the case of service clients, there is no user interaction, there is no password, so how do these service clients get valid tokens to call other services?&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Service Client tokens
&lt;/h2&gt;

&lt;p&gt;Just as we have end user tokens we'll want to have service tokens as well. To make security in the platform simple and consistent, these tokens should have the exact same form as the user tokens. That means they should look exactly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service-client-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.authress.io/tokens"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll notice here that instead of the user ID present in the &lt;code&gt;sub&lt;/code&gt; claim from above, we want to see the service client ID.&lt;/p&gt;

&lt;p&gt;Users get tokens by navigating through the authentication login flow. That flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Users register with a username, email, biometrics, WebAuthN, Face ID, etc...&lt;/li&gt;
&lt;li&gt;Then later, users navigate to the authentication service and use the same strategy as selected during registration.&lt;/li&gt;
&lt;li&gt;The user receives back a JWT that contains their username in the &lt;code&gt;sub&lt;/code&gt; claim.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We need a similar process for service clients as well:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register a service client and receive some credentials.&lt;/li&gt;
&lt;li&gt;Service client calls the authentication service with the credentials.&lt;/li&gt;
&lt;li&gt;The service client is returned a JWT that contains the service client ID in the &lt;code&gt;sub&lt;/code&gt; claim.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The credential generation options
&lt;/h2&gt;

&lt;p&gt;Step (3) is the same as with the user login case. This means as long as the service client interacts with the same &lt;strong&gt;Auth service&lt;/strong&gt;, they'll get back a valid service client JWT access token that can be easily verifiable. Step (2) can be accomplished if the auth service has an endpoint that accepts service client credentials and returns JWTs. The real question is how does step (1) happen, and what are credentials really?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Credentials&lt;/strong&gt; are the strategy in which service clients identify themselves. What's important is that these credentials provide the service client a way to do that. Further, we need the Auth service here, because we need some way of verifying the credentials that are generated. Without that, any service client could generate any credentials and impersonate both your users and other services. That means we need some service that is trustworthy.&lt;/p&gt;

&lt;p&gt;There are many ways for service clients to identify themselves. The core component is that the client can convey to the receiving service who they are. Some ways to do this are:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. A plain text string that says &lt;strong&gt;I'm Service X&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The service passes a string that literally says &lt;strong&gt;I am service X&lt;/strong&gt;. The problems with this should be obvious. It means that any service can pretend to be any other service. However, when you have only a couple of services, this might not be a problem. But it would require that all these services are protected behind some complex firewall, because if they are public, your services will not be able to distinguish between one of your valid services and a malicious attacker attempting to impersonate your services. Since your services are probably handling requests from users as well this doesn't work in real production environments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-XGET&lt;/span&gt; https://example.servire.com &lt;span class="nt"&gt;-H&lt;/span&gt;&lt;span class="s2"&gt;"Authorization: Service X"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. An api key
&lt;/h3&gt;

&lt;p&gt;When we say an API Key, we usually refer to a plain text string that is a generated by the Auth service, and is treated very similar to a password. When a service client wants a valid JWT it presents the API key, for which the Auth service can verify it. Often the API key is coupled to the specific service client. When you register the client in the Auth service, usually using the UI, you'll get back an API key. Many services with low security concerns out there will allow you to generate API keys for service client to interact with. Using API keys are susceptible to database vulnerabilities as well as potentially timing attacks. This is reviewed further in other academy articles, and won't be discussed here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffurytzmqbhryaz9smhj3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffurytzmqbhryaz9smhj3.png" alt="API Key creation for service client" width="800" height="655"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Generated x509 certificate
&lt;/h3&gt;

&lt;p&gt;x509 certificates are a complex strategy that enable the client to encrypt requests using that certificate. That are usually used in a scheme called mTLS. The problem with mTLS is that it requires a trusted certificate exchange in order to even generate the certificate.&lt;/p&gt;

&lt;p&gt;If you can't guarantee the certificate exchange is secure then this opens opportunities for vulnerabilities, making it worse than an plain text api key. Additionally the generation of these certificates not easily done. Lastly, they often don't provide a meaningful level of security. That is, while they are secure, their generation is difficult, it is difficult to keep it secure, and they probably won't help at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Certificate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3 (0x2)&lt;/span&gt;
        &lt;span class="na"&gt;Serial Number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;04:00:00:00:00:01:15:4b:5a:c3:94&lt;/span&gt;
        &lt;span class="na"&gt;Signature Algorithm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sha1WithRSAEncryption&lt;/span&gt;
        &lt;span class="na"&gt;Issuer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;C=BE, O=GlobalSign nv-sa, OU=Root CA&lt;/span&gt;
        &lt;span class="na"&gt;Subject&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;C=BE, O=GlobalSign nv-sa, OU=Root CA&lt;/span&gt;
        &lt;span class="na"&gt;Subject Public Key Info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Public Key Algorithm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rsaEncryption&lt;/span&gt;
                &lt;span class="s"&gt;Public-Key&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;(2048 bit)&lt;/span&gt;
                &lt;span class="s"&gt;Modulus&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;00:da:0e:e6:99:8d:ce:a3:e3:4f:8a:7e:fb:f1:8b&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="s"&gt;...&lt;/span&gt;
                &lt;span class="na"&gt;Exponent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;65537 (0x10001)&lt;/span&gt;
    &lt;span class="na"&gt;Signature Algorithm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sha1WithRSAEncryption&lt;/span&gt;
         &lt;span class="s"&gt;d6:73:e7:7c:4f:76:d0:8d:bf:ec:ba:a2:be:34:c5:28:32:b5:&lt;/span&gt;
         &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the key exchange, payloads can be directly decrypted by the API, and verified that way.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. An attestation provided by a third party service
&lt;/h3&gt;

&lt;p&gt;Another way to provide assurances that a request is coming from the right service client, is to use yet another system that provides some sort of attestation for these requests. Systems such as WS-Federation, Kerberos tickets, and others, exist in the world. Most of these are no longer used prevalently because they lack the sophisticated configurability, are unnecessarily complicated to set up, lack integrations with other tools, do not provide managed or cloud native solutions, or just did not follow a standard such as OAuth.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Ed25519 public/private key pairs
&lt;/h3&gt;

&lt;p&gt;The last option available is using asymmetric public/private key pars to sign and verify requests. This is the most secure option available. While some providers offer public/private key signatures, they use the weaker RS256 encryption method, however, this is still better than any of the other above alternatives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EdDSA signatures created via Ed25519 is the norm&lt;/strong&gt;. These pairs are created either by the Auth Service or the Service client and then exchanged. The Auth Service gets the public key, the Service Client gets the private key. From that point on, for every HTTP interaction request the service client will sign or create JWTs which the Auth service can verify. If the Auth service can verify the request from the signature that means other services can use the Auth service to verify these requests as well.&lt;/p&gt;

&lt;p&gt;For the remainder of this academy article, we will assume that service clients use a &lt;code&gt;private key&lt;/code&gt; in the Ed25519 form. There are many reasons for this, but most of them boil down to alternatives are either unnecessarily complicated or unsafe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Securing request chains using JWT tokens
&lt;/h2&gt;

&lt;p&gt;Now that we know how &lt;strong&gt;Credentials&lt;/strong&gt; are created for our service client, we'll need to start using them. Here will review a few different request flows.&lt;/p&gt;

&lt;h3&gt;
  
  
  End user request flow
&lt;/h3&gt;

&lt;p&gt;Here we'll review the flow when one of your users using your UIs makes a request to your service API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2wkjlr7lavyppra7k2vd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2wkjlr7lavyppra7k2vd.png" alt="Using JWTs to authenticate machine services" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The User gets a JWT access token by logging and now has it available in your UI.&lt;/li&gt;
&lt;li&gt;From there your UI makes an API request to &lt;code&gt;Service A&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Service A&lt;/code&gt; needs data from &lt;code&gt;Service B&lt;/code&gt; so it makes a subsequent request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this circumstance, Service A can actually pass along the user's JWT access token to Service B. If the resources in Service B are actually owned by the User, then Service A doesn't need to generate its own token, it can utilize the User's.&lt;/p&gt;

&lt;p&gt;This is the same flow that happens when a service asks for access to use your Google Drive. The token generated in the UI is passed through your service back to Google Drive to authenticate.&lt;/p&gt;

&lt;p&gt;When Service A and Service B receive the User's JWT access token in the request, they must verify that token and then it must also check the valid authorization. We have to do this to make sure the user actually has access to the resource they are asking for.&lt;/p&gt;

&lt;p&gt;Both services can verify the token the same way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.authress.io/tokens"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;SIG&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To verify the token, we will open it, grab the &lt;code&gt;iss&lt;/code&gt;, ask the &lt;code&gt;iss&lt;/code&gt; for the public keys associated with this token. Once we get back the public keys can we use them to verify the signature of the token.&lt;/p&gt;

&lt;p&gt;Examples of how to authorize requests are available in the &lt;a href="https://authress.io/knowledge-base/docs/authentication/validating-jwts#example-verifiers" rel="noopener noreferrer"&gt;Authress Knowledge Base&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Direct service authorization
&lt;/h3&gt;

&lt;p&gt;Sometimes however we can't use the end user's JWT. That's because one of these resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The end user doesn't own the resources in Service B. They could be Service A's private resources, the security to access a database is one example. That means the user's JWT won't have access.&lt;/li&gt;
&lt;li&gt;The resources don't exist yet, and we need to create them. Sometimes resources can only be created by service clients and then granted to users. Resources before they are created are either claimable by anyone using something called &lt;a href="https://authress.io/app/#/api?route=post-/v1/claims" rel="noopener noreferrer"&gt;Resource Claims&lt;/a&gt;, or are owned by Service A. In the case they are owned by Service, then Service A needs to call Service B as itself.&lt;/li&gt;
&lt;li&gt;The service isn't owned by you, but is actually owned by a third party developer who develops apps or plugins for your platform. This means your third party won't necessarily know what to do with your user's JWT AND even if they did, you would not want to grant them access by giving them a valid user token. (See more about this in &lt;a href="https://authress.io/knowledge-base/docs/extensions" rel="noopener noreferrer"&gt;Platform Extensions&lt;/a&gt;.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these cases, &lt;code&gt;Service A&lt;/code&gt;, will need to use its credentials to generate a valid JWT and then call &lt;code&gt;Service B&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using the credentials, &lt;code&gt;Service A&lt;/code&gt; can sign a request asking for a JWT, and then send that signed request to the &lt;code&gt;Auth Service&lt;/code&gt;. It will then get back a JWT that contains it's client ID as the &lt;code&gt;sub&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service-client-A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"client_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service-client-A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.authress.io/tokens"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;SIG&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, this same service client can perform what is known as &lt;strong&gt;Offline authentication&lt;/strong&gt; by using their private key to generate a service client minted JWT:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service-client-A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"client_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service-client-A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.authress.io/clients/service-client-A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;SIG&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll notice here, the &lt;code&gt;issuer&lt;/code&gt; has changed to be one that identifies &lt;code&gt;Service A&lt;/code&gt; as the issuer. Whether or not you choose offline or online authentication for your service clients is an implementation detail. It is more consistent to perform online, but offline offers a huge number of benefits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional: Bring your own keys (BYOK)
&lt;/h2&gt;

&lt;p&gt;Now with every integration between services secure we can technically move on to more important things. However, the secure storage of credentials is also not a trivial problem. More details are in &lt;a href="https://authress.io/knowledge-base/articles/securely-store-client-key-secret#using-encrypted-credentials" rel="noopener noreferrer"&gt;how to securely store credentials&lt;/a&gt;. We'll remember from above, the critical components are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A public/private key pair to sign and verify JWT tokens&lt;/li&gt;
&lt;li&gt;An Auth service to store the public keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means Auth services, including &lt;a href="https://authress.io" rel="noopener noreferrer"&gt;Authress&lt;/a&gt; don't necessarily care where the public/private key pair comes from. Any pair can be used, so long as it provides modern asymmetric cryptography. For Authress, this means you can generate your own EdDSA keys or even bring your AWS Key Management Service (KMS) keys to use with Authress. For this, only updating the public key is required and can be done as part of your CI/CD process or via the the &lt;a href="https://authress.io/app/#/api?route=post-/v1/clients/-clientId-/access-keys" rel="noopener noreferrer"&gt;Authress Service Client API&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why not send the service client credentials on every request?
&lt;/h3&gt;

&lt;p&gt;It works the same as with &lt;strong&gt;user passwords&lt;/strong&gt; in browsers. You only send your password on the login page, then the site generates a session credential. For every subsequent request only the session credential is used and not the password. This is so that the password is not present in every request. Ideally, the &lt;strong&gt;login page&lt;/strong&gt; is a separate website that has more security around dependency management and development workflows to prevent password &amp;lt;=&amp;gt; session token attacks. Now, the login page specifically has to be compromised instead of any one of a numerous number of front end applications. That's easy to control for. Additionally, the more services that have access to credential generation processes, such as the &lt;strong&gt;password&lt;/strong&gt;, the larger your attack surface is, and the more places the &lt;strong&gt;password&lt;/strong&gt; can end up in logs.&lt;/p&gt;

&lt;p&gt;Further, other services have no idea what do with the service client credentials. &lt;code&gt;Service B&lt;/code&gt; can't handle &lt;code&gt;Service A&lt;/code&gt; credentials only the &lt;code&gt;Auth service&lt;/code&gt; knows they are valid. Worse still is that if the credentials are sent, then &lt;code&gt;Service B&lt;/code&gt; could impersonate &lt;code&gt;Service A&lt;/code&gt; and get access &lt;code&gt;Service A&lt;/code&gt;'s private resources. Giving someone else your credential, is the same as giving them your password, they can impersonate you. Services are no exception to this rule.&lt;/p&gt;




&lt;p&gt;For help understanding this article or how you can implement a solution like this one in your services, feel free to reach out to the &lt;a href="https://authress.io/app/#/support" rel="noopener noreferrer"&gt;Authress development team&lt;/a&gt; or follow along in the &lt;a href="https://authress.io/knowledge-base/docs/category/introduction" rel="noopener noreferrer"&gt;Authress documentation&lt;/a&gt; and,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://authress.io/community" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7kfcgr5kmkwi1rxu471k.png" alt="Join the community" width="348" height="66"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>security</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>AWS Advanced: Serverless Prometheus in Action</title>
      <dc:creator>Warren Parad</dc:creator>
      <pubDate>Tue, 22 Aug 2023 13:09:09 +0000</pubDate>
      <link>https://dev.to/authress/aws-advanced-serverless-prometheus-in-action-j1h</link>
      <guid>https://dev.to/authress/aws-advanced-serverless-prometheus-in-action-j1h</guid>
      <description>&lt;p&gt;(Note, this article continues from Part 1: &lt;a href="https://dev.to/wparad/aws-metrics-advanced-40f8"&gt;AWS Metrics: Advanced&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  We can't use Prometheus
&lt;/h2&gt;

&lt;p&gt;It turns out Prometheus can't support serverless. Prometheus works by polling your service endpoints fetching data from your database and storing it. For simple things you would just expose the current "CPU and Memory percentages". And that works for virtual machines. It does not work for ECS Fargate, and definitely does not work for AWS Lambda.&lt;/p&gt;

&lt;p&gt;There is actually a blessed solution to this problem. Prometheus suggests what is known as a &lt;a href="https://prometheus.io/docs/practices/pushing/" rel="noopener noreferrer"&gt;PushGateway&lt;/a&gt;. What you do is deploy yet another service which you run, and you can push metrics to. Then later, Prometheus can come and pick up the metrics by polling the PushGateway.&lt;/p&gt;

&lt;p&gt;There is zero documentation for this. And that's because Prometheus was built to solve one problem, K8s. Prometheus exists because K8s is way to complicated to monitor and track usage yourself, so all the documentation that you will find is a yaml file with some meaningless garbage in it.&lt;/p&gt;

&lt;p&gt;Also we don't want to run another service that does actual things that's both a security concern as well as a maintenance burden. But the reason why PushGateway exists, to supposedly solve the problems of serverless, is confusing, because why doesn't Prometheus just support pushing events directly to it? And if you look closely enough at the AWS console for Prometheus, you might also notice this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs7vludkr60dp5zvdiu8b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs7vludkr60dp5zvdiu8b.png" alt="Remote write hint for prometheus" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's a &lt;code&gt;Remote Write Endpoint&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;You've got me, because there is no documentation on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  🗎 Promotheus RemoteWrite Documentation
&lt;/h2&gt;

&lt;p&gt;So I'm going to write here the documentation for everything you need to know about Remote Writing, which Prometheus does support. Although you won't find any documentation anywhere on it, and I'll explain why.&lt;/p&gt;

&lt;p&gt;Throughout your furious searching on the internet for how to get Prometheus working with Lambdas and other serverless technology, you will no doubt find a larger number of articles trying to explain how different types of metrics work in Prometheus. But metric types are a lie. They don't exist, they are fake, ignore them.&lt;/p&gt;

&lt;p&gt;To explain how remote write works, I need to first explain what we've learned about Prometheus.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prometheus Data Storage strategy
&lt;/h3&gt;

&lt;p&gt;Prometheus stores time series, that's all it does. A time series has a number of a labels, and a list of values at a particular time. Then later provides those time series to query in an easy way. That's it, that's all there is.&lt;/p&gt;

&lt;p&gt;Metric types exist, because the initial source of the metric data doesn't want to think about time series data, so Prometheus SDKs offer a bunch of ways for you to &lt;code&gt;create&lt;/code&gt; metrics, the internal SDKs convert those metric types to different time series and then these time series are hosted on a &lt;code&gt;/metrics&lt;/code&gt; endpoint available for Prometheus to come by and use.&lt;/p&gt;

&lt;p&gt;This is confusing I know. It works for &lt;code&gt;# of events of type A happened at Time B&lt;/code&gt;. But it does not support &lt;code&gt;average response time for type T&lt;/code&gt;? I'll get to this later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling data transfer
&lt;/h3&gt;

&lt;p&gt;Because you could have multiple Prometheus services running in your architecture, they need to communicate with each other. This is where RemoteWrite comes it. RemoteWrite is meant for you to run your own Prometheus Service and use RemoteWrite to copy the data from one Prometheus to another one.&lt;/p&gt;

&lt;p&gt;That's our ticket out of here. We can fake being a Prometheus service and publish our time series to the AWS Managed Prometheus service directly. If we fake being a Prometheus Server then as long as we fit the API used for handling this, we can actually push metrics to Prometheus. 🎉&lt;/p&gt;

&lt;p&gt;The problem here is that most libraries don't support even writing to the RemoteWrite url. We need to figure out how to write to this url now that we have it and also how to secure it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Prometheus SDK
&lt;/h3&gt;

&lt;p&gt;Luckily in nodejs there is the &lt;code&gt;prometheus-remote-write&lt;/code&gt; library. It supports AWS SigV4, which means that we can put this library in a Lambda + APIGateway service and proxy requests to Prometheus through it. It also sort of handles the messy bit with the custom protobuf format. (Remember K8s was created by Google, so everything is more complicated than it needs to be). With APIGateway we can authenticate our microservices to call the metrics microservice we need to build. The API can take the request and use IAM to secure the push to Prometheus. (It's worth noting here, you can actually push from one AWS account to another's Promotheus Workspace, but trying to get this to work is bad for two reasons--you never want to expose one AWS account's infra services to another, that's just a bad architecture, and two trying to get a lambda to assume a role and then pass the credentials correctly to the libraries that need them to authenticate is a huge headache).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwenqby1x3jy890qzqre0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwenqby1x3jy890qzqre0.png" alt="Metrics microservice architecture" width="800" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is the whole code of the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createSignedFetcher&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sigv4-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pushTimeseries&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prometheus-remote-write&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cross-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signedFetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSignedFetcher&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aps&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fetch&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://aps-workspaces.eu-central-1.amazonaws.com/workspaces/ws-00000000-0000-0000-00000000/api/v1/remote_write&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signedFetch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;series&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;series&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pushTimeseries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;series&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And just like that we now have data in Prometheus...&lt;/p&gt;

&lt;p&gt;But where is it?&lt;/p&gt;

&lt;h2&gt;
  
  
  ???
&lt;/h2&gt;

&lt;p&gt;So Prometheus has no viewer, unlike DynamoDB and others, AWS provides no way to look at the data at Prometheus directly. So we have no idea if it is working. The API response tells us &lt;code&gt;200&lt;/code&gt;, but like good engineers we aren't willing to trust that. We've also turned on Prometheus logging and that's not really enough help. How the hell do we look at the data?&lt;/p&gt;

&lt;p&gt;At this point we are praying there is some easy solution for displaying the data. My personal thought is this is how AWS gets you, AWS Prometheus is cheap, but you have to throw AWS Grafana on top in order to use it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0x0jydk1ypydskfvvoqj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0x0jydk1ypydskfvvoqj.png" alt="Grafana pricing" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that says $9 per user. Wow that's expensive to just look at some data. I don't even want to create graphs, just literally show me the data.&lt;/p&gt;

&lt;p&gt;What's really cool though is Grafana Cloud offers a near free tier for just data display, and that might work for us:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Funax09gpjl5a6uaotqhc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Funax09gpjl5a6uaotqhc.png" alt="Grafana Cloud Pricing" width="406" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well at least the free tier makes it possible for us to validate our managed Prometheus service is getting our metrics.&lt;/p&gt;

&lt;p&gt;And after way too much pain and suffering it turns out it is!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvup7rgv194dvvv3yl240.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvup7rgv194dvvv3yl240.png" alt="First pass of prometheus" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, we only sent three metric updates to Prometheus, so why are there so many data points. The problem is actually in the response from AWS Prometheus. If we dive down into the actual request that Grafana is making to Prometheus we can see the results actually include all these data points. That means it isn't something weird with the configuration of the UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmbh1u6osu2mhigxm90bv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmbh1u6osu2mhigxm90bv.png" alt="Too many duplicated data points" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm pretty sure it has to do with the fact that the &lt;code&gt;step size is 15s&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnerud95gm39y48sfysyh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnerud95gm39y48sfysyh.png" alt="Image description" width="800" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It doesn't really matter that it does this because all our graphs will be continuous anyway and expose the connected data. Also since this is a sum over the timespan, we should absolutely treat this as a minimum a 15s resolution unless we actually do get summary metrics more frequently.&lt;/p&gt;

&lt;p&gt;No matter what anyone says, these graphs are beautiful. It was the first thing that hit me when I actually figured out what all the buttons were on the screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🗸 Metrics stored in database&lt;/li&gt;
&lt;li&gt;🗸 Cost effective storage&lt;/li&gt;
&lt;li&gt;🗸 Display of metrics&lt;/li&gt;
&lt;li&gt;🗸 Secured with AWS or our corporate SSO&lt;/li&gt;
&lt;li&gt;🗸 Low TCO to maintain metric population&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Solution has been:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Prometheus&lt;/li&gt;
&lt;li&gt;Lambda Function&lt;/li&gt;
&lt;li&gt;Grafana Cloud&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Some lessons here:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Grafana UX is absolutely atrocious
&lt;/h3&gt;

&lt;p&gt;Most of the awesome things you want to do aren't enabled by default. For instance, if you want to connect Athena to Grafana so that your bucket can be queried, you first have to enable the plugin for Athena in Grafana. And only then can you create a DataSource for Athena. It makes no sense why everything is hidden behind a plugin. The same is true for AWS Prometheus, it doesn't just work out of the box.&lt;/p&gt;

&lt;p&gt;Second, even after you do that, your plugin still won't work. The datasource can't be configured in a way that works, that's because the data source configuration options need to be separately enabled by filing a support ticket with Grafana. I died a bit when they told me that.&lt;/p&gt;

&lt;p&gt;In our products &lt;a href="https://authress.io" rel="noopener noreferrer"&gt;Authress&lt;/a&gt; and &lt;a href="https://standup-and-prosper.com" rel="noopener noreferrer"&gt;Standup &amp;amp; Prosper&lt;/a&gt; we take great pride in having everything self-service, that also means our products' features are discoverable by every user. Users don't read documentation. That's a fact, and they certainly don't file support tickets. That's why every feature has a clear name and description next to it to explain what it does. And you never have to jump to the docs, but they are there if you need them. We would never hide a feature behind a hidden flag that only our support has access to.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The documentation for Prometheus is equally bad
&lt;/h3&gt;

&lt;p&gt;Since Prometheus was not designed to be useful but instead designed to be used with K8s, there is little to no documentation using Prometheus to do anything useful. Everyone assumes you are using some antiquated technology like K8s and therefore the metrics creation and population is done for you. So welcome to pain, but at least now there is this guide so you too can effectively use Prometheus in a serverless fashion.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Always check the AWS quotas
&lt;/h3&gt;

&lt;p&gt;The default retention is 150 days but this can be increased. One of the problems with AWS CloudWatch is that if you mess up you have 15 months of charges. But here we start with only 6 months. That's a huge difference. We'll plan to increase this, and I'm sure it will be configurable by API later.&lt;/p&gt;

&lt;p&gt;Just remember you need to review quotas for every new service you start using so you don't get bitten later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/prometheus/latest/userguide/AMP_quotas.html" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mynzeeve3iijx62t0ea.png" alt="AWS Prometheus Quotas" width="800" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Lacks the polish of a secure solution
&lt;/h3&gt;

&lt;p&gt;Grafana needs to be able to authenticate to our AWS account in order to pull the data it needs. It should only do this one of two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses the current logged-in Grafana user's OAuth token to exchange for a valid AWS IAM role&lt;/li&gt;
&lt;li&gt;Generates an OIDC JWT that can be registered in AWS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And yet we have...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9aaubmdy8p6cd2tsut9p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9aaubmdy8p6cd2tsut9p.png" alt="Grafana's Prometheus authentication options" width="800" height="626"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It does neither of these. Nor does it support dynamic function code to better support this. Sad. We are forced to use an AWS IAM user with an access key + secret. Which we all know you &lt;a href="https://dev.to/authress/when-to-use-aws-credentials-2ki4"&gt;should never ever use&lt;/a&gt;. And yet we have to do it here.&lt;/p&gt;

&lt;p&gt;I will say, there is something called &lt;a href="https://grafana.com/docs/grafana-cloud/connect-externally-hosted/configure-private-datasource-connect/" rel="noopener noreferrer"&gt;Private DataSource Connections (PDC)&lt;/a&gt;, but it isn't well documented if this actually solves the problem. Plus if it did, that we means we'd have to write some &lt;code&gt;GO&lt;/code&gt;, and no one wants to do that.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Prometheus metric types are a lie
&lt;/h3&gt;

&lt;p&gt;Earlier I mentioned that perhaps you want metrics that are something other than a time series. The problem is Prometheus actually doesn't support that. That's confusing because you can find guides like this &lt;a href="https://prometheus.io/docs/concepts/metric_types/" rel="noopener noreferrer"&gt;Prometheus Metric Types&lt;/a&gt; which lists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Counter&lt;/li&gt;
&lt;li&gt;Gauge&lt;/li&gt;
&lt;li&gt;Histogram&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;li&gt;etc...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also you'll notice that pathetic lack of libraries in nodejs and Rust.&lt;/p&gt;

&lt;p&gt;How can these metric types exist in a time series way? The truth is they can't. And when Prometheus says you can have these, what it really means is that it will take your data and mash it into a time series even if it doesn't work.&lt;/p&gt;

&lt;p&gt;A simple example is API Response Time. You could track request time in milliseconds, and then count the number of requests that took that long. There would be a new timeseries for ever possible request time. That feels really wrong.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5 requests at 1ms&lt;/li&gt;
&lt;li&gt;6 requests at 2ms&lt;/li&gt;
&lt;li&gt;1 requests at 3ms&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But that's essentially how Prometheus works. We can do slightly better, and the solution here is to understand how the Histogram configuration works for Prometheus. What actually happens is that we need to a priori decide useful buckets to care about. For instance, we might create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Less than 10ms&lt;/li&gt;
&lt;li&gt;Less than 100ms&lt;/li&gt;
&lt;li&gt;Less than 1000ms&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then when we get a Response Time, we add a ++1 to each of the buckets that it matches. A 127ms request would only be in the 1000ms bucket, but a 5ms would be in all three.&lt;/p&gt;

&lt;p&gt;Later when querying for this data you can filter on the buckets (not the response time) that you care about. Which means something like 1000 10ms step buckets may make sense or&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;N buckets in 1ms steps from 1-20ms&lt;/li&gt;
&lt;li&gt;M buckets in 5ms steps from 20-100ms&lt;/li&gt;
&lt;li&gt;O buckets in 20ms steps from 100-1000ms&lt;/li&gt;
&lt;li&gt;P Buckets in 1s steps from 1000ms+&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's about ~100 buckets to keep track off. Depending on your SLOs and SLAs you have you might need SLIs that are different granularities.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏁 The Finish Line
&lt;/h2&gt;

&lt;p&gt;We were almost done and right before we were going to wrap up we saw this error:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;err: out of order sample.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;What the hell does that mean? Well it turns out that Prometheus cannot handle timestamp messages out of order. What the actual hell!&lt;/p&gt;

&lt;p&gt;Let me say that again, Prometheus does not accept out of order requests. Well that's a problem. It's a problem because we batch our metrics being sent. And we are batching them because we don't have all the data available. And we don't have it because CloudWatch doesn't send it to us all at once nor in order&lt;/p&gt;

&lt;p&gt;We could wait for CloudWatch the requisite ~24 hours to update the metrics. But there is no way we are going to wait for 24 hours. We want our metrics as live as possible, it isn't critical that they are live, but there is no good reason to wait. If the technology does not support it then it is the wrong tech (does it feel wrong yet, eh, maybe...). The second solution is use the hack flag &lt;code&gt;out_of_order_time_window&lt;/code&gt; that Prometheus supports. Why it doesn't support this out of the box makes no sense. But also turns out things like mySQL and ProstreSQL didn't support updating a table schema without a table lock for the longest time. The problem is that at the time of writing AWS does not let us set this [out_of_order_time_window flag](&lt;a href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#tsdb" rel="noopener noreferrer"&gt;https://prometheus.io/docs/prometheus/latest/configuration/configuration/#tsdb&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That only leaves us with ~two solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ignore out of order processing, and drop these metrics on the floor&lt;/li&gt;
&lt;li&gt;ignore the original timestamp of processing the message and just publish &lt;code&gt;now&lt;/code&gt; as the timestamp on every message.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Guess which one we decided to go with.... That's right, we don't really care about the order of the metrics. It doesn't matter if we got a spike exactly at 1:01 PM or 1:03 PM, most of the time, no one will see notice this difference or care. When there is a problem we'll trust our actual logs way more than the metrics anyway, metrics aren't the source of truth.&lt;/p&gt;

&lt;p&gt;And, I bet you thought that is what I was going to say. And it's actually true, we would be okay with that solution. But the problem is that this STILL DOES NOT FIX PROMETHEUS. You still will end up with out of order metrics, and so the solution for us was add the CloudFront logs filename as a unique key as a &lt;code&gt;label&lt;/code&gt; in Prometheus, so our labels look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;response_status_code_total&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;_unique_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudFrontFileName&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember Labels are just the Dimensions in CloudWatch, they are how we will query the data. And with that, now we don't get any more errors. Because &lt;code&gt;out of order data points&lt;/code&gt; only happen within a single time series. By specifying the &lt;code&gt;_unique_id&lt;/code&gt; this causes the creation of one time series per CloudFront log file. (Is this okay thing to do? Honestly it's impossible to tell because there is zero documentation on how exactly this impacts Prometheus at scale. Realistically there are a couple of other options to improve upon this like having a random &lt;code&gt;_unique_id&lt;/code&gt; (1-10) and retrying with a different value if it doesn't work. This would limit the number of unique time series to 10.)&lt;/p&gt;

&lt;p&gt;Further, since we "group" or what Grafana calls "sum" by the other labels anyway, the extra _unique_id label will get automatically ignored:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuf82tuyo5a5ovy6jgbji.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuf82tuyo5a5ovy6jgbji.png" alt="How to create groups in Grafana" width="477" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  And the result is in!
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6in6eo0gfhfmwaskaxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6in6eo0gfhfmwaskaxa.png" alt="Response Status Codes" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's the total cost:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$0.00&lt;/strong&gt;, wow!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwjhg3cqikp258fbda0jm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwjhg3cqikp258fbda0jm.png" alt="Total AWS Prometheus cost" width="800" height="248"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Come join our &lt;a href="https://authress.io/community/" rel="noopener noreferrer"&gt;Community&lt;/a&gt; and discuss this and other security related topics!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://authress.io/community" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7kfcgr5kmkwi1rxu471k.png" alt="Join the community" width="348" height="66"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>architecture</category>
      <category>microservices</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>AWS Metrics: Advanced</title>
      <dc:creator>Warren Parad</dc:creator>
      <pubDate>Tue, 22 Aug 2023 13:06:05 +0000</pubDate>
      <link>https://dev.to/authress/aws-metrics-advanced-40f8</link>
      <guid>https://dev.to/authress/aws-metrics-advanced-40f8</guid>
      <description>&lt;p&gt;Normally I'm the last proponent of collecting metrics. The reason is: &lt;strong&gt;metrics don't tell you anything&lt;/strong&gt;. And anything that tells you nothing is an absolute waste of time setting up. However, alerts tell you a lot. If you know that something bad is happening then you can do something about it.&lt;/p&gt;

&lt;p&gt;The difference between alerts and metrics is &lt;code&gt;Knowing what's important&lt;/code&gt;. If you aren't collecting metrics yet, the first thing to do is decide what is a problem and what isn't. It's far to often I feel like I'm answering the question of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do I know if the compute utilization is above 90%.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer is, it doesn't matter, because if you knew, what would you do with that information. Almost always the answer is "I don't know" or "my director told me to it was important".&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚠ So what is important
&lt;/h2&gt;

&lt;p&gt;That's probably the hardest question to answer with a singular point. So for the sake of this article to make it concrete and relevant, let me share what's important for us.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Up-time requirements&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We've been at an inflection point within Rhosys for a couple of years now. For &lt;a href="https://authress.io" rel="noopener noreferrer"&gt;Authress&lt;/a&gt; and &lt;a href="https://standup-and-prosper.com" rel="noopener noreferrer"&gt;Standup &amp;amp; Prosper&lt;/a&gt; we run highly reliable services. These have to be up at least 4 nines, and often we contract out for SLAs at 5 nines. But the actual up-time isn't what has been relevant anymore. Because as most reliability experts know your service can be up, but still returning a 5XX here and there. This is what's known as partial degradation. You may think one 5XX isn't a problem. However for millions of requests per day, this amounts to a non-trivial amount. Even if it is just one 5XX per day, it absolutely is important, if you don't know why it happened. It's one thing to ignore an error because you know why, it's quite another to ignore it because you don't.&lt;/p&gt;

&lt;p&gt;Further, even returning 4XXs is often a concern for us as well. Too many 400s could be a problem. They could tell us something is wrong. From a product standpoint, a 4XX means that we did something wrong in our design, because one of our users should never get to the point where they get back a 4XX. If they get back a 4XX that means they were confused about our API or the data they are looking at. This is critically important information. Further, a 4XX could mean we broke something in a subtle way. Something that used to return a 2XX now returning a 4XX unintentionally means that we broke at least one of our users implementations. This is very very bad.&lt;/p&gt;

&lt;p&gt;So, actually what we want to know is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are there more 400s now than there should be?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A simple example is when a customer of ours calls our API in and forgets to &lt;code&gt;URL Encode&lt;/code&gt; the path, that usually means they accidentally called the wrong endpoint. For instance if the UserId had a &lt;code&gt;/&lt;/code&gt; in it then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route: &lt;code&gt;/users/{userId}/data&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Incorrect Endpoint: &lt;code&gt;/users/tenant1/user001/data&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Correct Endpoint: &lt;code&gt;/users/tenant1%2Fuser001/data&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When this happens we could tell the caller &lt;code&gt;404&lt;/code&gt;, but that's actually the wrong thing to do. It's the right error code, but it conveys the wrong message. The caller will think there is no data for that user, even when there is. That's because the normal execution of the endpoint returns &lt;code&gt;404&lt;/code&gt; when there is no data associated with the user or the user doesn't exist. Instead, when we detect this, we return &lt;code&gt;422: Hey you called a weird endpoint, did you mean to call this other one&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A great Developer Experience (DX) means returning the right result when the user asked for something in the wrong way. If we know there is a problem, we can already return the right answer, we don't need to complain about it. However, sometimes that's dangerous. Sometimes the developer thought they were calling a different endpoint, so we have to know when to guess and when to return a &lt;code&gt;422&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In reality, when we ask &lt;code&gt;Are there more 400s than there should be&lt;/code&gt;, we are looking to do anomaly detection on some metrics. If this is an issue we know to look at the recent code released and see when the problem started to happen.&lt;/p&gt;

&lt;p&gt;This is the epitome of using anomaly detection. Are the requests we are getting at this moment what we expect them to be, or is there something unexpected and different happening.&lt;/p&gt;

&lt;p&gt;To answer this question, finally we know we need some metrics. So let's take a look at our possible options.&lt;/p&gt;




&lt;h2&gt;
  
  
  🗳 The metric-service candidates
&lt;/h2&gt;

&lt;p&gt;We use AWS heavily, and luckily we AWS has some potential solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/devops-guru/" rel="noopener noreferrer"&gt;AWS DevOps Guru&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/lookout-for-metrics/" rel="noopener noreferrer"&gt;AWS Lookout anomaly detection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Anomaly_Detection.html" rel="noopener noreferrer"&gt;AWS CloudWatch Alarms with anomaly detection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So we decided to try some of these out and see if any one of them can support our needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  🗸 The Verdict
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;All terrible&lt;/strong&gt;. So we had the most interest in using DevOps Guru, the problem is that it just never finds anything. It just can't review our logs to find problems. The one case it is useful is for RDS queries and to determine if you need an index. What happens when you fix all your indexes? Then what?&lt;/p&gt;

&lt;p&gt;After turning on Guru for a few weeks, we found nothing*. Okay, almost nothing, we found a smattering of warnings regarding the not-so-latest version of some resources being used, or permissions that aren't what AWS thinks they should be. But other than that, it was useless. You can imagine for an inexperienced team, having DevOps Guru enabled will help you about as much as Dependabot does at discovering actual problems in your GitHub repos. The surprise however, is that it is cheap.&lt;/p&gt;

&lt;p&gt;DevOps Guru - cheap but worthless.&lt;/p&gt;

&lt;p&gt;Then we took a look at AWS Lookout for Metrics. It promises some sort of advanced anomaly detection on your metrics. AWS Lookout is actually used for other things, not primarily metrics, so this was a surprised. And it seemed great, exactly what we are looking for. And when you look at the price it appears reasonable for $0.75 / metric. We only plan on having a few metrics, right? So that shouldn't be a problem. Let's put this one in our back pocket while we investigate the CloudWatch anomaly detection alarms.&lt;/p&gt;

&lt;p&gt;At the time of writing this article we already knew something about anomaly detection using CloudWatch Alarms. The reason is we have anomaly detection set on some of our AWS WAF (Web Application Firewalls). Here's an example of that anomaly detection:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuvz3nllc41a5j90szkfo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuvz3nllc41a5j90szkfo.png" alt="WAF total requests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see there is a little bit of red here where the results were unexpected. This looks a bit cool, although it doesn't work out of the box. Most of the time we ended up with the dreaded alarm flapping sending out alerts at all hours of the day.&lt;/p&gt;

&lt;p&gt;To really help make this alarm useful we did three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A lot of trial and error with the period and relevant datapoint count to trigger the alarm&lt;/li&gt;
&lt;li&gt;The number of deviations outside of the norm to be considered an issue, is the change 1 =&amp;gt; 2 a problem or is 1 =&amp;gt; 3 a problem&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;logarithm&lt;/code&gt; based volume&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, while those numbers on the left aren't exactly the &lt;code&gt;Log(total requests)&lt;/code&gt;, they are something like that. And this graph is the result. See logarithms are great here, because the anomaly detection will throw an error as soon as it's outside of a band. And that band is magic, you've got no control over that band for the most part. (You can't choose how to make it think, but you can choose how thick it is)&lt;/p&gt;

&lt;p&gt;We didn't really care that there are 2k rps instead of 1.5rps for a time window, but we do care that there are 3k rps or 0 rps. So the logarithm really makes more sense here. Magnitudes different is more important.&lt;/p&gt;

&lt;p&gt;So now we know that the technology does what we want we can start.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⏵ Time to start creating metrics
&lt;/h2&gt;

&lt;p&gt;The WAF anomaly detection alarm looks great, though it isn't prefect, but hopefully AWS will teach the ML over time what is reasonable and let's pray that it works. (I don't have much confidence in that). But at least it is a pretty good starting point. And since we are going to be creating metrics, we can reevaluate the success afterwards and potentially switch to AWS Lookuout for Metrics if everything looks good.&lt;/p&gt;

&lt;h2&gt;
  
  
  💰 $90k
&lt;/h2&gt;

&lt;p&gt;Now that's a huge bill. It turns out we must have done something wrong, because according to AWS CloudWatch Billing and our calculation we'll probably end up paying $90k this month on metrics.&lt;/p&gt;

&lt;p&gt;Let's quickly review, we attempted to log APM metrics (aka Application Performance Monitoring) using CloudWatch metrics. That means for each endpoint we wanted to log:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The response status code - &lt;code&gt;200&lt;/code&gt;, &lt;code&gt;404&lt;/code&gt;, etc..&lt;/li&gt;
&lt;li&gt;The customer account ID - &lt;code&gt;acc-001&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The HTTP Method - &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The Route - &lt;code&gt;/v1/users/{userId}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's one metric, with these four dimensions, and at $0.30 / custom metric / month, we assumed that means $0.30 / month. &lt;strong&gt;However, that is a lie&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;AWS CloudWatch Metrics charges you not by metric, but by each unique basis of dimensions, that means that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/users{userId}&lt;/code&gt; returning a 200 for customer 001&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DELETE /v1/users{userId}&lt;/code&gt; returning a 200 for customer 002&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Are two different metrics, a quick calculation tells us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~17 response codes used per endpoint (we heavily use, 200, 201, 202, 400, 401, 403, 404, 405, 409, 412, 421, 422, 500, 501, 502, 503, 504)&lt;/li&gt;
&lt;li&gt;~5 http verbs&lt;/li&gt;
&lt;li&gt;~100 endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since metrics are saved for ~15 months, using even one of these status codes in the last 15 months will add to your bill. But this math shows us it will cost ~$2550 / &lt;strong&gt;per customer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you only had 100 customers for your SaaS, that's going to cost you &lt;code&gt;$255,000&lt;/code&gt; per month. And we have a lot more customers than that. Thankfully we did some testing first before releasing this solution.&lt;/p&gt;

&lt;p&gt;I don't know how anyone uses this solution, but it isn't going to work for us. We aren't going to pay this ridiculous extortion to use the metrics service, we'll find something else. Worse still that means that AWS Lookout for metrics is also not an option, because it would cost us 75% of that cost per month as well, so let's just call it $5k per customer per month. Now, while I don't mind shelling out $5k customer for a real solution to help us keep our 5-nines SLAs, it's going to have to do a lot more than just keep a database of metrics.&lt;/p&gt;

&lt;p&gt;We are going to have look somewhere else.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚑 SaaS to the rescue?
&lt;/h2&gt;

&lt;p&gt;I'm sure some one out there is saying we use ________ (insert favorite SaaS solution here), but the truth is none of them support what we need.&lt;/p&gt;

&lt;p&gt;Datadog and NewRelic were long eliminated from our allowed list because they resorted to malicious marketing phone calls directly to our personal phone numbers multiple times. That's disgusting. Even if we did allow one of those to be picked, they are really expensive.&lt;/p&gt;

&lt;p&gt;What's worse, all the other SaaS solutions that provide APM fail in one of these ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI/UX is not just bad, but it's terrible,&lt;/li&gt;
&lt;li&gt;don't work with serverless&lt;/li&gt;
&lt;li&gt;are more expensive that Datadog, I don't even know how that is possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But wait, didn't AWS release some managed service for metrics...&lt;/p&gt;

&lt;h2&gt;
  
  
  𓁗 Enter AWS Athena
&lt;/h2&gt;

&lt;p&gt;AWS Athena isn't a solution, it's just a query on top of S3, so when we say "use AWS Athena" what we really mean is "stick your data in S3". But actually we mean:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Stick your data in S3, but do it in a very specific and painstaking way. It's so complicated and difficult that AWS wrote a whole second service to take the data in S3 and put it back in S3 differently.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That service is called &lt;code&gt;Glue&lt;/code&gt;. We don't want to do this, we don't want something crawling our data and attempting to reconfigure it, it just doesn't make sense. We already know the data at the time of consumption. We get a timespan with some data, and we write back that timespan. Since we already know the answer, using a second service dedicated to creating timeseries doesn't make sense. (It does absolutely make sense if we had non-timeseries data, and needed to convert it to timeseries for querying, but we do not).&lt;/p&gt;

&lt;p&gt;The real problem here however, is that every service we have would need to figure out how to write this special format to S3 so that it could be queried. Fuck.&lt;/p&gt;

&lt;p&gt;While we could build out a &lt;code&gt;Timeseries-to-S3 Service&lt;/code&gt;, I'd rather not. The Total Cost of Ownership (TCO) of owning services is really really high. We knew this already before we built our own statistics platform and deprecated it, we don't want to do it again.&lt;/p&gt;

&lt;p&gt;So Athena was out. Sorry.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔥 Enter Prometheus
&lt;/h2&gt;

&lt;p&gt;And no I'm not talking about Elastic Search, that doesn't scale, and it isn't really managed. It's a huge pain. It's like taking a time machine back to the days on on-prem DBAs, except these DBAs work at AWS and are less accessible.&lt;/p&gt;

&lt;p&gt;The solution is AWS runs a managed Grafana + Prometheus solutions. The true SaaS is Grafana Cloud, but AWS has a managed version, and of course these are also two different AWS services.&lt;/p&gt;

&lt;p&gt;Apparently Prometheus is a metrics solution. It keeps track of metrics and it's pricing is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2y28sh0qk9t8m17vtu0a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2y28sh0qk9t8m17vtu0a.png" alt="Prometheus pricing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We know we aren't going to have a lot of samples (aka API requests to prometheus), so let's focus on the storage...&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$0.03/GB-Mo&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Looking at a metric storage line as &lt;code&gt;status code + verb + path + customer ID&lt;/code&gt; we get ~200B, which is &lt;code&gt;$1e-7&lt;/code&gt; / request + storage) For $100 / month that would afford us &lt;code&gt;847,891,077&lt;/code&gt; API requests per month. (This assumes &lt;a href="https://docs.aws.amazon.com/prometheus/latest/userguide/AMP_quotas.html" rel="noopener noreferrer"&gt;~150 days by default&lt;/a&gt; retention time). Let's call it 1B requests per month, that's a sustained 400rps. Now while that is a fraction of where we are at, the pricing for this feels so much better. We pay only for what we use, and the amortized cost per customer also makes a lot more sense.&lt;/p&gt;

&lt;p&gt;So we are definitely going to use Prometheus it seems.&lt;/p&gt;

&lt;p&gt;For how we did that exactly, check out Part 2: &lt;a href="https://dev.to/wparad/aws-advanced-serverless-prometheus-in-action-j1h"&gt;AWS Advanced: Serverless Promtheus in Action&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;If you liked this article come join our &lt;a href="https://authress.io/community/" rel="noopener noreferrer"&gt;Community&lt;/a&gt; and discuss this and other security related topics!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>architecture</category>
      <category>monitoring</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Denylists and Invaliding user access</title>
      <dc:creator>Warren Parad</dc:creator>
      <pubDate>Wed, 05 Jul 2023 14:15:26 +0000</pubDate>
      <link>https://dev.to/authress/denylists-and-invaliding-user-access-1cgl</link>
      <guid>https://dev.to/authress/denylists-and-invaliding-user-access-1cgl</guid>
      <description>&lt;p&gt;This article is part of the &lt;a href="https://authress.io/knowledge-base/academy/topics"&gt;Authress Academy&lt;/a&gt; and discusses the different ways to invalidate a user's access and revoke their tokens.&lt;/p&gt;

&lt;p&gt;It discusses solutions for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Denylists&lt;/li&gt;
&lt;li&gt;OAuth JWT token expiry&lt;/li&gt;
&lt;li&gt;API Gateways&lt;/li&gt;
&lt;li&gt;Refresh Tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;To secure communication between different systems or services that you have, an access token is sent between the user, client, or machine to your service API. Usually, this is some sort of JWT access token.&lt;/p&gt;

&lt;p&gt;In the case of Authress and other providers, a JWT token is generated when the user logs in, that token is available for them to gain access to their resources and prove their identity to your services.&lt;/p&gt;

&lt;p&gt;When that token expires, they must then fetch a new token. (Fetching new tokens won't be discussed in this article, the KB has additional articles on &lt;a href="https://authress.io/knowledge-base/docs/category/authentication"&gt;Authress Authentication&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;However, there are cases where we want to terminate access as soon as possible--User role changes, permission revocation, or token exposures--all may facilitate the need to terminate access.&lt;/p&gt;

&lt;h2&gt;
  
  
  OAuth JWT access tokens + Scopes
&lt;/h2&gt;

&lt;p&gt;Many authentication solutions and identity providers generate JWT access tokens and provide these as a way to authenticate users to your applications. They are generated for the user in the UI, and are sent to your services via the &lt;code&gt;Authorization&lt;/code&gt; header. Generated JWTs are usually created through a standardized OAuth exchange, the details of which aren't immediately relevant here, but there are a number of other Authress KB articles that discuss the topic. For the rest of the article, we'll assume that you know what a JWT access token and how to get one. (If you are unsure, please don't hesitate to join our &lt;a href="https://authress.io/community"&gt;Community&lt;/a&gt; and ask!)&lt;/p&gt;

&lt;p&gt;In the case of authentication solutions that do not offer any sort of authorization or access control, they may attempt to store a user role (or roles) in the JWT in the &lt;code&gt;scope&lt;/code&gt; JWT property claim.&lt;/p&gt;

&lt;h3&gt;
  
  
  OAuth JWT Scopes
&lt;/h3&gt;

&lt;p&gt;The OAuth JWT scope claim is a property which contains a list of permissions and roles that the user has approved to be passed to the JWT. However, since the User gets their own JWT, this may seem unnecessary complex. The standard OAuth use case is that the requestor of the JWT is not the same as the User themselves. That is, your user is actually approving the generation of the JWT by your UI. And then your service will validate the JWT. Scopes here are a way to restrict what permissions the user is passing to the token. When this JWT is used, the &lt;code&gt;scopes&lt;/code&gt; provide a way to filter the resources that are actually allowed to be accessed.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user has access to all their own resources of the types: documents, videos, photos&lt;/li&gt;
&lt;li&gt;A UI could request the scopes &lt;code&gt;documents&lt;/code&gt; and &lt;code&gt;photos&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The user approves the UI requests for those scopes
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://login.authress.io"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"documents photos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1685021390&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1685107790&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openid profile email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"https://api.authress.io"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated JWT would only contain the &lt;code&gt;documents&lt;/code&gt; and the &lt;code&gt;photos&lt;/code&gt; scopes and not the &lt;code&gt;videos&lt;/code&gt; one. That means of all the resources that the User has access to, the JWT only has access to user's documents and photos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhv41bunrjjse5oo0xhry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhv41bunrjjse5oo0xhry.png" alt="Scope restricted resources" width="798" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll notice that scopes are not the same as permissions the user has. The user has permissions to only some resources, but the scopes have even more limited access. Scopes constrain access based on what the user already has permissions to. The optimal use of OAuth JWT Scopes are used when you don't control both the UI and the Service, and there are two reasons for that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Most Authentication providers allow the User to specify any and all scopes that they want in the token. This means if you are using the Scopes to control access to resources that the user can interact with, you have a vulnerability. You are letting your users control what they have access to. Scopes must not be used for access control for the User.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You'll need some to capture which permissions the user actually gave to another service. If a third party is accessing your resources or you are accessing a third party's resources, the &lt;code&gt;scopes&lt;/code&gt; granted to the client application, control that access. This is a different list of permissions because it is a different entity performing the API request.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Issues with scopes
&lt;/h3&gt;

&lt;p&gt;Since scopes are embedded into the JWT token, as long as the JWT token is valid (between the &lt;code&gt;nbf&lt;/code&gt; and &lt;code&gt;exp&lt;/code&gt; times) then the holder of that token has access to the user's resources that are granted via the scopes in the token. This means that there is no way to block requests in a situation where you want to invalidate the access that the token grants.&lt;/p&gt;

&lt;p&gt;This is the OAuth spec, and while it seems like a missing part of the specification, if we consider the difference between the access granted to the user and the scopes granted to the third party on behalf of the user, it makes sense that in most cases this doesn't matter. Invalidating a JWT wouldn't make sense.&lt;/p&gt;

&lt;p&gt;This also makes sense when we consider what these JWT tokens are. A JWT access token is a representation of a user identity. The Authentication Service generates the token to represent the user's identity, and then the user, client, or third party presents that token to prove who they are. However, it doesn't really say anything about what they can access or why. Further, JWTs don't stop representing the user just because their access changes.&lt;/p&gt;

&lt;p&gt;As discussed in Academy for &lt;a href="https://authress.io/knowledge-base/academy/topics/access-control-strategies"&gt;access control strategies&lt;/a&gt; and &lt;a href="https://authress.io/knowledge-base/academy/topics/offline-attenuation"&gt;token attenuation&lt;/a&gt;:&lt;/p&gt;

&lt;h3&gt;The most common mistake is putting the roles or permissions into the JWT.&lt;/h3&gt;

&lt;p&gt;When the permissions are in the JWT (using the &lt;code&gt;scope&lt;/code&gt; or a custom claim), the permissions have become coupled to the identity of the user. These are separate things, and deserve separate treatment, and we'll go into some of the further issues and resolutions below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Revoking a JWT access token
&lt;/h2&gt;

&lt;p&gt;Now that we know that the permissions coupling to the JWT access token creates some problems, we can discuss what we can do about it.&lt;/p&gt;

&lt;h3&gt;
  
  
  0. Do nothing approach
&lt;/h3&gt;

&lt;p&gt;The default and simple approach is to do nothing. For most implementations and products, this may be the best answer, however if you are in Healthcare, Banking, or another highly regulated government industry, this is not going to be the right approach, and skipping this step is recommend.&lt;/p&gt;

&lt;p&gt;When the JWT expires the user will lose access to the scopes and permissions that were specified in the token. This may seem bad from a security standpoint, however in most cases, this is exactly the flow that makes the most sense. Most cases don't need to be concerned with malicious attackers using still valid JWTs. When the user signs out of the UI, discard the JWT. Once it leaves memory it should no longer be a problem. If this were a problem that the hypothetically saved JWT could still be used, we can question &lt;strong&gt;How?&lt;/strong&gt; If the token is available to be used after log out, and we are concerned that it will be, how did that come to happen?&lt;/p&gt;

&lt;p&gt;One common answer is that the user is using a shared machine. However on shared machines, user log out is not reliable, and shared machines are not truth-worthy. Simply logging out, even if the token were revoked will not prevent vulnerabilities from existing to utilize the token and impersonate the user. It is an illusion that there is a solution to this problem other than terminating the OS of the machine (which still might not be sufficient.)&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Limiting the access token lifetime
&lt;/h3&gt;

&lt;p&gt;Going further would be to ensure that the lifetime of the token is as limited as possible. With authentication session management, we can generate an additional token for the user when the current one expires. So instead of 24 hours, 4 hours, or even 1 hour, if the token expiry is in 5 minutes, then every 5 minutes the user will get a new token. Even if the user doesn't log out, that token is going to expiry in the next 5 minutes, almost completely reducing the feasibility of an attack using an exfiltrated token.&lt;/p&gt;

&lt;p&gt;Using Authress or another centralized authentication identity provider, you might be able to configure the token lifetime to automatically expire.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs86y5e285ikrlnk2biyk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs86y5e285ikrlnk2biyk.png" alt="Short lived token lifetimes" width="741" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Use a shared Denylist
&lt;/h3&gt;

&lt;p&gt;In a platform where you have many services, creating an endpoint on one service that allows the user to logout thereby storing that timestamp, isn't sufficient to ensure that the token with the scopes doesn't get used again. This leads to the need to have a unified solution for token management. We already know we need a unified solution for authentication, but this adds an additional layer of complexity. One common solution is the creation of an API gateway. An API Gateway is a reverse-proxy for all the services in your platform.&lt;/p&gt;

&lt;p&gt;The API Gateway receives every request, verifies that the token is valid based on your identity provider and internal token cache, and then forwards the request or denies it. Additionally, it can offer an endpoint that allows your services to revoke a still valid identity token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F99cie8wfdo1y00pgtlc6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F99cie8wfdo1y00pgtlc6.png" alt="API Gateway implementation" width="574" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The drawback with this is that it requires this additional piece of technology on top of your existing Authentication IdP, and it also introduces a requirement that your user logout now needs to call to your exposed API Gateway in order to invalidate these tokens. There are some solutions that make storing this data in a database easier, but it is still something extra you'll need to add to your API Gateway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distributed Cache Alternative:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you aren't interested in setting up an API Gateway, which often is infeasible in a multi-region deployment or in a highly distributed system, then alternatively a distributed shared caching solution can work. The Authress recommendation is to avoid having a shared cache wherever possible, since it increases the cost of maintenance and often serves as a single point of failure that wasn't designed to handle fault tolerance at the scale that IdPs are designed for. (See &lt;a href="https://authress.io/knowledge-base/docs/advanced/authress-downtime-protections"&gt;Authress downtime protections&lt;/a&gt; for some of these fault tolerant protections.)&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Permission changes
&lt;/h3&gt;

&lt;p&gt;So far we have only discussed the need to invalidate credentials when the user logs out, but what happen when their roles and permissions change? Your API Gateway isn't going to be sufficient to know what to do, unless you add permission and role changes to it as well. At that point you've designed your own AuthZ solution, and it would be better to use an existing IdP as part of your reverse-proxy solution. (And in that case, you might want to check out &lt;a href="https://authress.io/knowledge-base/articles/so-you-want-your-own-authorization"&gt;Building your own AuthZ solution&lt;/a&gt;.) And worse, if you invalidate the token in the API Gateway, your users in your UI never know they need to get a new token. The invalidation happened on the service side without the users' knowledge. They'll start getting back &lt;code&gt;401&lt;/code&gt; errors from the API Gateway. A workaround would be to add handling code to your UI to force a re-login when a 401 is seen as a response from a service. But then this code has to be replicated to every UI you have, and not every service might be behind the API Gateway, that means special handling would also be necessary for invalidations to know which service the user is calling from the UI. Some calls can expect a &lt;code&gt;401&lt;/code&gt; while others a &lt;code&gt;401&lt;/code&gt; means "my roles changed". That can cause a poor UX for the user, if they are forced to log out every time their roles are changed.&lt;/p&gt;

&lt;p&gt;The best solution here is to decouple the authorization from the token itself. By storing the authorization access control and permissions for the user in a separate service from the user identity handling, when the access changes, then no further updates are necessary.&lt;/p&gt;

&lt;p&gt;That's because authorization is checked realtime instead of only being populated during token generation. With JWT scopes, the permissions and roles are cemented into the token, but with an Authorization solution the access is dynamic. When roles change, so does the user's live access. This is a significant improvement, because there is no extra storing of access tokens nor a need to invalidate them one by one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawd3nlomw4k4oaeni3cd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawd3nlomw4k4oaeni3cd.png" alt="API Gateway implementation using Authress" width="573" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, depending on the solution you are using, in this case if we assume &lt;a href="https://authress.io/knowledge-base/docs/category/authorization"&gt;Authress Authentication&lt;/a&gt;, when the user logs out the user's session may also be terminated on the provider side. Meaning that a solution that offers both Authentication and Authorization and keeps them segregated works best for handling token invalidation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concerns
&lt;/h2&gt;

&lt;p&gt;One common concern as we go further down this list is the reliance more and more on a shared centralized system. And most shared systems are not fault tolerant--they weren't build to scale nor built to be reliable. Many open source implementations of token invalidation, caching, and gateways suffer from this lack of reliability. And the more reliable they were designed the more difficult they are to maintain, that's because they pass the burden of maintenance onto the development team that runs the open source solution. It's better to go with high SLA AuthN solution that already supports your needs out of the box.&lt;/p&gt;

&lt;p&gt;Rather than having to build this yourself and maintain it, finding the right product that fits your needs is a must. The current &lt;a href="https://authress.io/knowledge-base/articles/auth-situation-report"&gt;auth situation report&lt;/a&gt; is available in the KB for a deep dive on the different auth technology pieces.&lt;/p&gt;

&lt;p&gt;Here are some hints about how reliable these solution are:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Reliability designed in
&lt;/h3&gt;

&lt;p&gt;High SLA services (at least four 9's), are designed with this distributed reliability in mind. It isn't a single point of failure since frequently the service itself has been replicated to multiple regions in multiple datacenters with additional backups. your technology often can be anywhere in the world and your users can be on the opposite side, and both your services and the user can experience very-low latency. This is often achieved with distributed CDNs and edge-node authentication.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Selection of the right protocol and technology
&lt;/h3&gt;

&lt;p&gt;When designing token invalidations, a large part of the ability to even execute effectively, requires picking technologies that are by default high performance. Some providers offer the &lt;a href="https://authress.io/knowledge-base/docs/authorization/service-clients/authress-implementation"&gt;EdDSA&lt;/a&gt; token signature standard which is faster and produces smaller signatures. Utilizing distributed public keys, enables a hands-off approach to verifying credentials. Using one of these providers that supports public key encryption enables tools like an API Gateway or the services themselves to verify tokens without even making any API calls. The public keys are cacheable for an extended period of time. Enabling fast requests without the single-point of failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Clear separation of responsibilities
&lt;/h3&gt;

&lt;p&gt;Solutions that recommend storing the access permissions inside the identity JWT immediately get off to a bad start. We've seen from above this encourages coupling in a way which cases issues at the important security edge cases. While it can seem like an optimization for simplicity, it actually just creates problems. There are very few products, services, and applications where JWT scopes for permissions actually is a good fit. Therefore, the selection of a technology that provides the separation between identity and access control is important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going further
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What about refresh tokens
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Refresh tokens&lt;/code&gt; are part of the OAuth standard that exist to enable third party services to impersonate users even when the user's JWT access token has expired. That has nothing to do with token invalidation and don't help us at all here in our use case.&lt;/p&gt;

&lt;p&gt;Revocation comes up often with OAuth, because documentation sources often point to Refresh tokens as a solution. That's because in the OAuth spec, Refresh tokens can actually be revoked and solutions, recommendations, and implementations get stuck on this part. Refresh tokens are long lived, and revoking them prevents new access tokens from being generated from that Refresh token. But just because the refresh token is revoked, does not mean that the access token is revoked. And in almost all implementations, that is the case, because access tokens are validated on the client side, but the revocation database is on the service side. So attempting to use refresh token revocation to block access token usage would require every API request to unnecessarily call out to the revocation database to verify the token. This converts the standard distributed offline public key process for token verification to be online, centralized, and slow.&lt;/p&gt;




&lt;p&gt;For help understanding this article or how you can implement a solution like this one in your services, feel free to reach out to the &lt;a href="https://authress.io/app/#/support"&gt;Authress development team&lt;/a&gt; or follow along in the &lt;a href="https://authress.io/knowledge-base/docs/category/introduction"&gt;Authress documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>api</category>
      <category>security</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Myths about API HTTP clients</title>
      <dc:creator>Warren Parad</dc:creator>
      <pubDate>Thu, 29 Jun 2023 08:38:42 +0000</pubDate>
      <link>https://dev.to/authress/myths-about-api-http-clients-ecc</link>
      <guid>https://dev.to/authress/myths-about-api-http-clients-ecc</guid>
      <description>&lt;p&gt;Having built many Product APIs in my experience for multiple companies, there are a number of Myths we've come to learn about APIs in general. Here are some of the more interesting ones we've learned through building &lt;a href="https://authress.io"&gt;Authress&lt;/a&gt;, which is an AuthN + AuthZ solution. For reference here is the &lt;a href="https://authress.io/app/#/api"&gt;Authress API&lt;/a&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Myths
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Clients should ignore properties in an HTTP response that they don’t understand.
&lt;/h2&gt;

&lt;p&gt;In reality they will start to depend on every undocumented property in every conceivable way &lt;a href="https://www.hyrumslaw.com/"&gt;Hyrum's Law&lt;/a&gt;. Our APIs are frequently used in unexpected ways. We go back through our logs every time before we make a change to see every call that was made (in a reasonable timeframe) to make sure we don't break any previously expected behavior. "How could our customers break if we change this" is not a game we play, it is a frequent topic of conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Clients should follow redirects if presented with them.
&lt;/h2&gt;

&lt;p&gt;But they never will. Clients will assume getting back anything other than &lt;code&gt;200&lt;/code&gt; is an error. Yes that is even &lt;code&gt;200&lt;/code&gt;, a &lt;code&gt;201&lt;/code&gt; will cause an error. So we have to be very careful when changing the status code we return, or even the &lt;code&gt;errorCode&lt;/code&gt; in the error response. Changing a 400 to a 422 will cause someone to break because they definitely wrote code that says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;statuscode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Clients should will follow the recommended HTTP status codes and headers to handle async requests.
&lt;/h2&gt;

&lt;p&gt;Not even remotely. Client usually assume all API calls are synchronous and instantaneous. That means the default behavior always has to be make sense, and it can never change. When you want to offer clients better functionality, it either has to be the default or put behind a documented RFC header flag like &lt;code&gt;prefer: async&lt;/code&gt;. Clients will call these endpoints way more than they should.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Clients will use your published API specification and SDKs.
&lt;/h2&gt;

&lt;p&gt;Instead, they'll make every kind of call to your API, they don't care if you can't handle it. If there is an API rate-limit or size limit, you are guaranteed that they will attempt to overcome it. They'll use every API software client known to humans to call your API not just Postman. And the bugs with those clients will become your problems, they'll often not work for some reason, and then you get a nice introduction to how yet-another-api-client has some weird defaults.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Clients can use your published API specification to make HTTP calls.
&lt;/h2&gt;

&lt;p&gt;Clients expect that your API is available in every programming language and every framework for every language. If it's not available in their preferred language...To bad, they'll just use a different service. And also they will find every bug in your SDK, not just actual bugs, issues with how a library dependency of a dependency of a dependency didn't interpret the RFC correctly and so now you need to make a bug fix to 3 other open source projects in order to get your customer working.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. You can make a secure Login/Signup portal for your users.
&lt;/h2&gt;

&lt;p&gt;No matter what you do, these unprotected endpoints will be spammed until you run out of money in your wallet. We know because we run &lt;a href="https://authress.io"&gt;Authress&lt;/a&gt; which provides this for our customers. And to no end do we get requests there. Don't have these endpoints, use a federated provider, or even better use the right identity aggregator for your use case by reviewing this &lt;a href="https://authress.io/knowledge-base/articles/auth-situation-report"&gt;Auth situation report&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. You will only get requests that make sense.
&lt;/h2&gt;

&lt;p&gt;Welcome to the world of bots, where every day your APIs will be spammed with an arbitrary list of fuzzers attempting to find exposed and unsecured MongoDBs, PHP WordPress instances, and any number of other requests with random &lt;code&gt;Authorization&lt;/code&gt; headers. Be prepared to handle the errors in your application that you didn't intend on because no human would have thought to make that API call. Everything that can go wrong, will. And those errors will always be the first to show up in your alert tracker.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Following the standard for what you need will work out--RFC, IETF, W3C.
&lt;/h2&gt;

&lt;p&gt;It would be nice if there was actually a standard for what you are working on, but at best there are just some documents that sort of look like what you want. Here's an awesome &lt;a href="https://github.com/kdeldycke/awesome-falsehood"&gt;curated list of falsehoods you might believe&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your clients will always request something that doesn't match the spec. Worse still they'll require it in order to use your API. The spec doesn't support it, not even remotely, leaving you in a really bad place. Worst of all, you know that they are right.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Supporting an integration with a third party for your customer is easy.
&lt;/h2&gt;

&lt;p&gt;Every API today needs to support a deluge of third party products to integrate with their solution. When we built Authrss, we had to integrate with just a couple of OAuth/SAML/OIDC/AD solutions. Now, the list goes on and on--Google, Azure, Steam, Zoho, Slack, Discord, MagicLinks, etc...-- And that's just the AuthN part, then we integrate with every cloud provider (AWS, GCP, Azure) to send our partner SIEM logs (SumoLogic, Elastic, DataDog), and still more for handling other types of data integrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Clients will cache reasonably and use your Cache-Control header to do so.
&lt;/h2&gt;

&lt;p&gt;Clients will never cache their responses unless they are somehow forced. Even when the data almost never changes, the idea of caching the data for even one or two seconds never crosses their mind. They will happily call your API for the same data over and over again. Rate-limiting will force them, at some point to make a change.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Your rate limits will make sense for you users.
&lt;/h2&gt;

&lt;p&gt;The only thing rate limiting does is create an expensive piece of technology you now need to maintain. If you have multiple services/applications, then you've created a very dangerous piece of code/library/reverse-proxy that one small bug will cause downtime for everyone.&lt;/p&gt;

&lt;p&gt;Further, rate-limits are surprises for your clients. Your clients will accidentally hit them one day and start causing huge problems, and that's because rate-limits aren't for them, they are for you. And so, if you make the mistake of adding rate-limits, now you need to monitor all the traffic in/out of your service to make sure non of your clients ever hit the rate-limit. Because if they do, it's the same as your service being down.&lt;/p&gt;

&lt;p&gt;Likely you don't actually even need the rate-limit, but one time, one person with too much authority said you needed them, and now you are stuck. Or worse still, you are handing out api keys to your customers and never thought about how to track and secure them. (This by the way is why Authress offers &lt;a href="https://authress.io/knowledge-base/docs/authorization/service-clients#enabling-your-users-to-call-your-apis"&gt;API keys as a service&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  12. We can release new versions of our API when something changes.
&lt;/h2&gt;

&lt;p&gt;This is probably the worst lie. No, you can never release a new version of your API. Sure, you can publish it, but no one will use it, or maybe only a few people will. Realistically, integrations don't get updated. If you release an endpoint today, assume you need to maintain it indefinitely, unless you want to have the conversation with your CEO about why you lost 90% of your customer base because you dropped a necessary endpoint and their product stopped working.&lt;/p&gt;

&lt;p&gt;No one wants to change anything that is working, don't try to make them. It's also worth mentioning here that there are ZERO consistent ways to actually change a REST api to support multiple versions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Different versions of the spec is a nightmare to maintain, and that's just the documentation part&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;v1.service.com&lt;/code&gt; - Using a subdomain is the worst possible idea, now instead of maintaining one service, you've got two, that do almost the exact same thing, 99% of the endpoints are the same, and one of them is only slightly different.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/v1/resources&lt;/code&gt; - Different versions in the resource path of the url, actually means these are different resources. If they are don't use a version here, just change the name of the resource&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;X-Header: v1&lt;/code&gt; A header is difficult to discover and worst still, there's no way to default it a new version&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resources?version=v1&lt;/code&gt; - Query parameters seam like the best possible solution, but have all the same problems as the headers--difficult to document, difficult to discover, difficult to know what the right one is.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the end of the day, don't release breaking changes to your API.&lt;/p&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Don't get stuck in the Myths of building an API. And if you find yourself wanting a more secure API in the process, you know where to go.&lt;/p&gt;

&lt;p&gt;Come join our &lt;a href="https://authress.io/community/"&gt;Community&lt;/a&gt; and discuss this and other security related topics!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>architecture</category>
      <category>development</category>
      <category>backend</category>
    </item>
    <item>
      <title>Breaking up the monolith: Breaking changes</title>
      <dc:creator>Warren Parad</dc:creator>
      <pubDate>Fri, 05 Aug 2022 19:17:05 +0000</pubDate>
      <link>https://dev.to/authress/breaking-up-the-monolith-breaking-changes-14dd</link>
      <guid>https://dev.to/authress/breaking-up-the-monolith-breaking-changes-14dd</guid>
      <description>&lt;p&gt;Before we get into how to handle a breaking change, we should first identify what is even a breaking change.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is a breaking change
&lt;/h1&gt;

&lt;p&gt;A breaking change is:&lt;br&gt;
&lt;strong&gt;&lt;em&gt;anything that causes a hypothetical client of your service using the service in anyway to start behaving differently.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s a broad statement, but it’s true. Even if you don’t make changes to the API, if you change the expectations around how endpoints work, it will break clients. Therefore it is a breaking change. It’s also important to realize that you might not know how every client is using your API, so whether or not there is a real client you can point to is irrelevant.&lt;/p&gt;

&lt;p&gt;Some examples of breaking changes might be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API interface property type is changed (from &lt;code&gt;int&lt;/code&gt; to &lt;code&gt;string&lt;/code&gt; for instance)&lt;/li&gt;
&lt;li&gt;Size of the data property is changed, (from 3 to 4 characters)&lt;/li&gt;
&lt;li&gt;Returning an additional enum value in an enum property where you didn’t first explain to the clients that this list can be expanded. The general recommendation is that this isn’t a breaking change. But remember it doesn’t matter if you think it is, it matters if your clients do.&lt;/li&gt;
&lt;li&gt;If you return an inconsistent or different &lt;code&gt;error code&lt;/code&gt; or &lt;code&gt;response status code&lt;/code&gt;. Returning a &lt;code&gt;400&lt;/code&gt; instead of a &lt;code&gt;404&lt;/code&gt; can be considered a breaking change. &lt;code&gt;404&lt;/code&gt; means something, it’s possible that the &lt;code&gt;404&lt;/code&gt; was a bug, and the resource really existed. So sometimes making a breaking change is a good thing.&lt;/li&gt;
&lt;li&gt;Allowing the schema type of a property be different in difference circumstances, i.e. returning an &lt;code&gt;int&lt;/code&gt; or a &lt;code&gt;string&lt;/code&gt;, just don’t do this. While it is possible to document the union types, it's a huge headache for development teams to deal with.&lt;/li&gt;
&lt;li&gt;Requiring a previously &lt;code&gt;optional&lt;/code&gt; property or requiring a new header to continue having previous functionality. Clients not sending the header or the property will now get a &lt;code&gt;400&lt;/code&gt; or &lt;code&gt;422&lt;/code&gt; back on their response, instead of the previous &lt;code&gt;2xx&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  How to handle breaking changes
&lt;/h1&gt;

&lt;p&gt;We when we run a service, either a UI or an API, that service has endpoints or URLs that point to representations of resources. When we change the schema/interface/expectations around how that endpoint works we are introducing what is known as a breaking change.&lt;/p&gt;

&lt;p&gt;One example of a breaking change is a changing a property in the response from type &lt;strong&gt;int&lt;/strong&gt; to type &lt;strong&gt;string&lt;/strong&gt;. The reason it is a breaking is because this change can cause a client of the API to incorrectly parse the response.&lt;/p&gt;

&lt;p&gt;If the client has code that says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (response.property * 10 &amp;lt; 100) { doSomething(); }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then how property will be handled by different languages might result in a runtime exception or worse, no exception but improper handling of the result.&lt;/p&gt;

&lt;p&gt;There’s obviously a need to introduce the string version of property, we don’t have to care where the need came from, but one example could be, we ran out of numbers. Transactional data runs into this problem all the time, and converting from a sequential int to a guid is one way to help.&lt;/p&gt;

&lt;p&gt;Note: I general solution to this problem is that &lt;strong&gt;all identifiers must always be strings&lt;/strong&gt; , &lt;strong&gt;never make an identifier an integer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the solution to the problem, but we might have made a mistake, and in hindsight this is obvious, but doesn’t help us.&lt;/p&gt;

&lt;p&gt;So what do we do?&lt;/p&gt;

&lt;h1&gt;
  
  
  Versioning endpoints
&lt;/h1&gt;

&lt;p&gt;One solution is to prefix all your endpoints (or use a header or query parameter) to tell the service which version of an endpoint to use. Let’s define what we mean by versioning an endpoint. Versioning an endpoint is not “running multiple versions of the service at the same time”. It means adding an indicator to the endpoint so that callers can select which version they want. While in practice this can be done, it actually isn’t a concept that is but into practice. Here we will see why.&lt;/p&gt;

&lt;p&gt;For instance we might have:&lt;/p&gt;

&lt;p&gt;GET /v1/demo and now we’ll introduce GET /v2/demo Where in v1 we return an integer version in the property field and in the v2 we return a string version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This works but it is a very bad and terrible idea&lt;/strong&gt;. The reason is that clients that want the new functionality have to find and update their code to reference the new version of the endpoint. Another reason is that you might have multiple changes in progress at one time, does the v3 endpoint contain two changes at once, or what if you have three changes, how does that even work.&lt;/p&gt;

&lt;p&gt;Another core problem here is that it just isn’t RESTful. While you might have something to say about whether REST is important, we should agree at the very least that this is a true statement. Having two different endpoints means that the resources at these endpoints should be different. Further what happens when you actually create a v2 resource and want to make the property be deadc0de? This resource now cannot be returned on the v1 endpoint, because v1 only understands int not the string value this property is.&lt;/p&gt;

&lt;p&gt;Still further issues include the increased complexity for clients that don’t care about this change, but care about dependent changes. They want to still use the v1 endpoint because they need int for right now, but have a critical change they need that you’ve released in v3. They can’t get it until they take the string upgrade.&lt;/p&gt;

&lt;p&gt;Not to mention the maintenance burden on the service side to now keep track of multiple endpoints. And even if we clean up in the end, we’ll have the problem that we’ve got an endpoint on v2.&lt;/p&gt;

&lt;p&gt;The last problem is visibility. Along with the complexity, we might not even have a way to solve the problem if we release an SDK. The SDK has hard coded the v1 endpoint, and it would be a mess if we had to introduce duplicate DTOs every time we wanted to make a small change. Not to mention the nightmare later, seeing as we’ll have the exact same problem there. Having breaking changes in a library, just moves the problem. And worse, it moves the problem to every library you maintain.&lt;/p&gt;

&lt;h1&gt;
  
  
  Further issues
&lt;/h1&gt;

&lt;p&gt;The issue is compounded even further if we have multiple endpoints. What happens if we have two endpoints:&lt;/p&gt;

&lt;p&gt;GET /v1/resource and POST /v1/resource And let’s say that property is a write only value that is only used in the POST . Now we have a huge discrepancy if we role the v1 POST to a v2. If it isn’t obvious, think about what happens when a different change is necessary only to the GET v1 endpoint. The v2 for the GET has now a totally different meaning than the v2 for the POST. A client updating their code won’t know the semantic meaning of v2 and doesn’t know that it means something different. This is creating a &lt;strong&gt;pit of failure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There’s a joke here about &lt;strong&gt;how many Haskell programmers does it take change a light bulb?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One, but you have to change the whole house.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can of course go to the extreme of releasing a new version of whole service with everything identical, and running both versions at the same time. When existing clients migrate to the new version, you can shutdown the old version.&lt;/p&gt;

&lt;p&gt;Please don’t do this, some clients will never migrate, and you will be stuck with duplicate consumed resources until the end of time. For very small early services, you are better off just breaking your clients.&lt;/p&gt;

&lt;h1&gt;
  
  
  One more example
&lt;/h1&gt;

&lt;p&gt;Twitter is a service, you can use it, and they release new features all the time. Surely there are breaking changes, but when you want to go to twitter, you go to &lt;a href="https://twitter.com"&gt;https://twitter.com&lt;/a&gt; you don’t go to &lt;a href="https://v2.twitter.com"&gt;https://v2.twitter.com&lt;/a&gt;. Even without doing so you still get new features automatically, there might even be breaking changes.&lt;/p&gt;

&lt;p&gt;“Now, now Warren, that’s not the same.”&lt;/p&gt;

&lt;p&gt;Okay, but bear with me. Even though the UI is a service, you never need to go to a different url to get new functionality even when the UX breaks your experience. Yes, twitter breaks your experience all the time. But it doesn’t break your client, not the best example, so let’s dive in.&lt;/p&gt;

&lt;p&gt;If we look at all the apis that are released in the world, the number of endpoint version changes that exist is minuscule and almost zero. It’s so small, that I’ve found telling my teams it’s better to not stick the v in the endpoint url at all. If you need a different resource for some reason, just create a different endpoint. If it is the same resource, then update the endpoint, but don’t break it.&lt;/p&gt;

&lt;p&gt;Go for it, go find a public API out there that has versioning and versions the api regularly. It doesn’t exist. Even GCPs APIs are mostly on v2. And this comes from a company that frequently deprecates things before they are released. Adding support for versioning in endpoints is over-engineering. Here’s &lt;a href="https://developer.twitter.com/en/docs/twitter-api"&gt;twitter v2 api&lt;/a&gt;, and it’s been around as a company since 2006, when your service is 16 years old, I’ll let you know that only slightly disappointed if you release a v2 :).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We don’t need to version endpoints&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  So, what’s the solution?
&lt;/h1&gt;

&lt;p&gt;If replacing the whole service is on one side of the spectrum, what’s on the other side?&lt;/p&gt;

&lt;p&gt;Is it regret that we have an int for the rest of time?&lt;/p&gt;

&lt;p&gt;I hope not.&lt;/p&gt;

&lt;p&gt;Instead what we can do is add a new property propertyString or propertyV2 propertyAdvanced propertyOtherThing.&lt;/p&gt;

&lt;p&gt;This is really easy, it doesn’t solve every problem, but you can return the new value in this new property, and leave the current one alone. In rare cases, when we created an issue with the primary key of the resource, obviously this won’t work, and creating a new resource/endpoint might be the only solution. Obviously this an edge case, but does happen. But rather than come up with “one solution to rule them all”, we would rather have a better solution to 99% of the problems, and an okay solution to the 1%.&lt;/p&gt;

&lt;p&gt;Later, after all the SDKs are updated and the client are using the new property, we can delete property. However even better, we could delete the property from our documentation, but leave the property available. There’s almost no reason to delete it, the cost is very small to have it, and in most cases trivial for maintenance.&lt;/p&gt;

&lt;p&gt;Adding a new property is no different than adding a feature. The only time you’ll need to do something special is when you go to delete the property. So treat it like everything else until then, a new separate property, and just don’t break the clients.&lt;/p&gt;

&lt;h1&gt;
  
  
  The interesting opportunities
&lt;/h1&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Situation 1: Just don’t change the damn thing.&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Leave it the way it is, I know you hate it, but honestly the work to change it isn’t worth the change. Sure you could make some updates, let clients choose how they want to call your service, and return the new updates. But if it is merely semantic, get over it. If you want to be a good engineer, focus on the business impact not whether or not you are unhappy with it being an int.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Situation 2: You coupled your DB to your API&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There are some frameworks that I consider atrocious, never should have been created and the software community is worse off for having them. I’m going to enumerate the list, but it comes down to anything that makes it easy to couple your DB schema to your API interface. (And don’t get me started on monolithic technologies that let you couple your DB to your UI presentation logic). Things like GrapQL can be good solutions to specific problems, but are often abused by inexperienced engineers to do exactly this.&lt;/p&gt;

&lt;p&gt;The critical thing to do here, is abstract your DB schema from the interface. Your clients don’t care about the DB schema, they care about the service interface. In the cases you need to make DB changes, if you can’t do so in a way that doesn’t cause a breaking change in your API, first separate these two. Create a serialization layer, an abstraction layer, an auto-mapper, a schemaless DB NoSQL solution, etc… It doesn’t really matter how you do it, as long as you do it. You will absolutely need to change your DB at some point, and you can’t let the rigidity of your API prevent you from doing so.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Situation 3: Security issues found!&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;It happens, you find a security vulnerability, some property in the API is either exposing data it shouldn’t be, exposing data to whom it shouldn’t be, or just not working correctly. You can’t go on exposing it, and so that required property you have is going to become optional, there’s no way around that.&lt;/p&gt;

&lt;p&gt;However, changing a property from required to optional in a response body is a breaking change, and clients depending a non-empty value will break. But there is really nothing you can do here, other than eat the vulnerability. There is no better time to remain security compliant than in the face of breaking clients. I know something about security in APIs, since I have designed these for many companies, a lot of clients would rather have their business fail then make improvements, sometimes you have to bite the bullet and let these service clients start throw exceptions. But if you do, please communicate that you are doing this.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Situation 4: Deprecation&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In the case you really really need to change the schema for non security reasons. Communicate it, even if you do need to change the schema for security reasons, communicate it. No reason to not always communicate.&lt;/p&gt;

&lt;p&gt;In the case of a service endpoint or service resources that you don’t need any more, either because they aren’t a business competency or because the cost of management is too high for any reason, remove them.&lt;/p&gt;

&lt;p&gt;You want to remove them, some of your clients might even want you to remove them. Your documentation is confusing, or service is confusing, just delete the endpoints.&lt;/p&gt;

&lt;p&gt;However you don’t want to break clients. So instead, come out with a deprecation plan. The best deprecation plans are between 6 months and 1 year, where you commit to turning off that endpoint. The trouble is even with all of this, clients will wait until that last email before telling you they can’t migrate. You can certainly try to avoid this, but that day is coming, and they are still using your legacy thing.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Conclusion
&lt;/h1&gt;

&lt;p&gt;Just don’t make breaking changes to interfaces, remember from the mistakes you made in your past, and deal with unnecessary extra properties, removing documentation for old features, and focusing on the new ones. Take the lessons an build better services, because the cost and headache to trying to fix it is so high. If you are okay with just breaking it for your clients, then just do it, trying to go around this problem is a waste of time and resources. Don’t make the breaking change either — by adding new properties, or by living with the properties you have.&lt;/p&gt;

&lt;h1&gt;
  
  
  Some Quick Advice
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Use strings for all fields that don’t represent numbers. If the property is a number than int is fine, if that property isn’t a number, please don’t use an int.&lt;/li&gt;
&lt;li&gt;All Ids should be strings&lt;/li&gt;
&lt;li&gt;Do not have mutually exclusive boolean properties: is_active is_deleted use status enums instead&lt;/li&gt;
&lt;li&gt;Use Objects instead of properties. Instead of otherResourceId use: otherResource: { id: '' } , then you can add additional properties later.&lt;/li&gt;
&lt;li&gt;Prefer Arrays to single elements, so when that thing expands you can be prepared to add additional objects to that object. It’s much easier to have an Array of a single object, than it is to explain when you have both a property called thing and another property called thingList.&lt;/li&gt;
&lt;li&gt;Resources usually don’t have versions, but resources can point to versions of other things. Audit trails and changelogs are a different thing.&lt;/li&gt;
&lt;li&gt;Before naming a property thingV2 try to come up with a more descriptive name, such as thingAdvanced or thingWithExtraStuff&lt;/li&gt;
&lt;li&gt;don’t have a type property at the top level. If you have two types called a and b then instead have a property bag called { a:{}, b: {} } where you can store the properties specific to each of those types.&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>rest</category>
      <category>microservices</category>
      <category>monoliths</category>
      <category>api</category>
    </item>
    <item>
      <title>AWS CloudWatch: How to scale your logging infrastructure</title>
      <dc:creator>Warren Parad</dc:creator>
      <pubDate>Mon, 30 May 2022 11:58:07 +0000</pubDate>
      <link>https://dev.to/authress/aws-cloudwatch-how-to-scale-your-logging-infrastructure-8j0</link>
      <guid>https://dev.to/authress/aws-cloudwatch-how-to-scale-your-logging-infrastructure-8j0</guid>
      <description>&lt;p&gt;An obvious story you might decide to tell yourself is &lt;strong&gt;Logging is easy&lt;/strong&gt;. And writing to the &lt;strong&gt;console&lt;/strong&gt; or &lt;strong&gt;printing&lt;/strong&gt; out debugging messages may seem easy, and when running a service locally it usually is. As soon as you cross the magical barrier that is the cloud, for some reason this gets really complicated.&lt;/p&gt;

&lt;p&gt;So complicated that so so so many companies that think they can compete on delivering this exact solution. But this isn’t an post about which of those to use, nor is it a marketing ploy for a specific provider. (And I’ve used a lot of them, and for some reason they are all terrible, the only thing that was more terrible than using a SaaS provider for logging, was running an open source thing, with ELK being the worst logging infrastructure ever created. Your logging infra should cost at most 10% of your spend and next to 0% of your development time. Yet when you use any provider, it’s like 50%)&lt;/p&gt;

&lt;p&gt;For something that usually costs around 30% of your total cloud spend, you would expect to get something useful out of logging. And you do, logging is critical for the sustainability of your service and your business. At Rhosys, we frequently need to know not only if our services are working, but how effectively they are working. Dashboards that monitor call counts and latencies are worthless to a business, we need to know exactly what business relevant logs look like. Like most security conscious companies (non-security conscious companies probably want to ignore what I say next, otherwise it will feel like a bit of holy water burning your internal devil), we have multiple AWS accounts each with a dedicated purpose assigned to only one team, only that one team that has access to that specific AWS account. You don’t share accounts.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Setup
&lt;/h3&gt;

&lt;p&gt;How we set them up is less important, what is important is that each product gets its own AWS account. It just makes sense, and it’s required when a different team owns each one. Since Rhosys has three core products (at the time of writing), we have something like 40 AWS accounts (because AWS of course):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 AWS account to run &lt;a href="https://authress.io/"&gt;Authress&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;1 AWS account to run &lt;a href="https://standup.teaminator.io/"&gt;Standup &amp;amp; Prosper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;1 AWS account to run &lt;a href="https://modulemancer.com/"&gt;Modulemancer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;1 AWS account for open source and a bunch of our partnerships with AWS&lt;/li&gt;
&lt;li&gt;1 account per developer&lt;/li&gt;
&lt;li&gt;and then tons more because why not&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn’t a story about security though, it’s about maintenance, and since each of our products is in a separate account, there are some complexities with actually figuring out the core problem of &lt;strong&gt;How is our service doing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Because we are using AWS and lots of serverless technologies, we make heavy use of CloudWatch Logs. CW Logs is great. It’s better than every other SaaS logging tool out there, it’s fantastic for monitoring as well. (But it’s terrible at alerting.) At this point we still don’t have a great solution to “report this problem to the dev team”, and that’s because CW Logs doesn’t offer a way to send an email or trigger an alert that &lt;strong&gt;Actually includes what is wrong&lt;/strong&gt;. This is because the &lt;strong&gt;monitoring solution&lt;/strong&gt; actually aggregates data instead of annotating and indexing it. And you’ll need to use SNS + CW Insights to help you.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Logs
&lt;/h3&gt;

&lt;p&gt;So back to focusing on our three product accounts. For the most part, and I’m glossing over some finer details, we log directly to CloudWatch logs, and it’s great. What isn’t great is if you wanted to see all the logs in one place (which is usually wrong, because different teams can have different solutions). But you might want to see all the — alerts, business problems, critical issues in a digestible format. This doesn’t have to be one place. It’s sufficient to have one CW dashboard per account, and easily switch between them.&lt;/p&gt;

&lt;p&gt;Another solution is multiple instances of log collection. That is deploy log aggregation services to every AWS account. The problem is that means we would need to run a worker in every account to handle logging. That’s wrong. Having to deploy an agent for every service or for every region, or even every AWS account, is bad architectural design, and it doesn’t scale. This has to be automated, and require near zero burden on accounts that opt in.&lt;/p&gt;

&lt;p&gt;Like the good microservice architects we are, we funnel the relevant business related logs to a secured logging account. For our expectation on how and what we log, there’s a separate article where I speak in depth about our expectations around &lt;a href="https://dev.to/wparad/hacking-your-product-support-strategy-4kl4"&gt;logging, their purpose, and how to get the most value out of them&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The TL;DR of that article is that we have log statements that look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[Action Required] Failed to automatically handle plan
upgrade, review and determine why it failed and how to
more gracefully improve this problem in the future.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ERROR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gets converted in CloudWatch Logs to a base64 mess that we need a complex handler to disentangle. This the is meat of our log aggregator:&lt;/p&gt;

&lt;p&gt;(Note: the &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/SubscriptionFilters.html#LambdaFunctionExample"&gt;awslogsData is actually list from CW&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;logEvent&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;awslogsData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logEvents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;parsedLogEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;logStream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;awslogsData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;logGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;awslogsData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;logEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extractedFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;extractedTimeStamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;logEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extractedFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;logEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extractedFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Handle timeouts explicitly&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Task timed out after&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;parsedLogEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; (RequestId: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parsedLogEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ERROR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Handle everything else&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// We want to pull out the JSON object from our logs&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventMatcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;INFO|TRACE|ERROR|WARN&lt;/span&gt;&lt;span class="se"&gt;)\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;(?:[\w&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\s]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)?(\{&lt;/span&gt;&lt;span class="sr"&gt;.*&lt;/span&gt;&lt;span class="se"&gt;\})\s&lt;/span&gt;&lt;span class="sr"&gt;*$/&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fallbackLevel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;eventMatcher&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INFO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loggedMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventMatcher&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

    &lt;span class="c1"&gt;// If the message is a special error which has the code === 'ForceRetryExecution' then ignore it, we use this for enabling internal retries&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;loggedMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;loggedMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;ForceRetryExecution&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/i&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Normalize a bunch of properties depending on exactly where the real message data is&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stringOrObjectMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;loggedMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;loggedMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;parsedLogEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;stringOrObjectMessage&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stringOrObjectMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stringOrObjectMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;parsedLogEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsedLogEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;fallbackLevel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;parsedLogEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsedLogEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;loggedMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;parsedLogEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsedLogEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;loggedMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;parsedLogEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsedLogEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;loggedMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Actually do something with the message&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsedLogEvent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So hopefully that abbreviated mess above shows where the value comes in our structured logging. Since all of our services log with structure, it’s easy for us to parse them and handle them in a unified way. I highly recommend a consist logging approach which is something like this. Using structured logs allows easy debugging of any service you have, without having to relearn a new pattern. Of course across teams this can be different, but then they’ll have their own needs and their own aggregation systems. And when you want to additional value to every account logging source, you can do it in one place without updating some library which you force every one of your services in every AWS account to update (as if that is even a real strategy).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fallacy
&lt;/h3&gt;

&lt;p&gt;The trouble is here, while we have a great way to handle logs and a great way to log data, we have no way to easily port the logs from one AWS account to another. You would think in the infinite ability of an IAM system that you would be able to assign a valid resource policy to the lambda function and use it across AWS accounts. Alas, you cannot. It turns out that building a successful authorization framework is a huge challenge, and while AWS did a great job thus far, we can attest that managing one and solving for every edge case is a Sisyphean burden. (How do we know? We did it using &lt;a href="https://authress.io/knowledge-base/so-you-want-your-own-authorization"&gt;Authress&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The only way to port logs from one AWS account to another in an automated fashion (remember we want a full service solution, we don’t want to deploy a log subscription lambda function to every region for every account), is to use AWS Kinesis OR AWS Kinesis Firehose.&lt;/p&gt;

&lt;p&gt;Wait, those are different things you say? YES they are!&lt;/p&gt;

&lt;p&gt;For lack of clear documentation from AWS, Kinesis is a shared database and Kinesis Firehose is a transport mechanism. So you can either stick the data from CloudWatch logs into a specialized shared DB (Kinesis) or you can delegate that work to transporting the data somewhere else (Kinesis Firehose). Since Kinesis Firehose forces you to stream to a DB. Your options are Database or Database. And that Database cannot be CloudWatch Logs, nor does Kinesis support calling lambda directly, because hey, WHY NOT!&lt;/p&gt;

&lt;p&gt;Since Kinesis is always on, it costs the wrong kind of money. We want full scalability, so we’ve gone with Firehose. Spin up a Firehose in the logging account, and use that with every CloudWatch subscription.&lt;/p&gt;

&lt;p&gt;I can just set a resource policy on my Firehose to allow my whole AWS org access to make subscriptions from CW Logs, right? NOPE, you need to create what are known as custom Log Destinations, and enable other accounts to use that. That’s multiple additional AWS resources to manage.&lt;/p&gt;

&lt;p&gt;Oh, also Kinesis Firehose isn’t a valid event source for lambda. “WHAT” — you say. That’s right, you need to funnel the data to an S3 bucket, and then use a Lambda trigger to actually hit the Log parsing Lambda Function.&lt;/p&gt;

&lt;p&gt;(And for fun, the data that comes into the lambda via S3 from Kinesis, isn’t delimited. The data is directly concat-ed. Why in the world does it not automatically put delimiters between the records by default, is beyond me. And nothing that a simple .replace(/}{/g, ‘}\n{’).split('\n') couldn’t fix)&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;As a result there are number of moving pieces to this which allow use to aggregate the logs in a single account for alerting purposes. Remember you want to also deploy this in every region, not just one. Your logs should stay in the same region they are generated in:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gDbRnTZn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AGXerV562eOvT_ez5kzKypA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gDbRnTZn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AGXerV562eOvT_ez5kzKypA.png" alt="" width="800" height="460"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Multiaccount AWS Architecture Diagram&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And the relevant CloudFormation Template to generate these resources in the Logging AWS Account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create the bucket where we temporarily store the logs:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;CrossAccountLogBucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::S3::Bucket&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;AccessControl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Private&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;BucketName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Sub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${AWS::AccountId}-${AWS::Region}-cross-account-logging-sink&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;NotificationConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;LambdaConfigurations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s3:ObjectCreated:*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LambdaFunctionAlias&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Allow it to directly invoke the Lambda Function LambdaFunctionAlias
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;S3LambdaInvokePermission&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::Lambda::Permission&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LambdaFunctionAlias&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lambda:InvokeFunction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;Principal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s3.amazonaws.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;SourceAccount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;Ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::AccountId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;SourceArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Sub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;arn:aws:s3:::${AWS::AccountId}-${AWS::Region}-cross-account-logging-sink&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create the Kinesis Firehose
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;LogDeliveryStream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::KinesisFirehose::DeliveryStream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;DeliveryStreamName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Sub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${serviceName}-${AWS::Region}-Log-Sink&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;ExtendedS3DestinationConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;BucketARN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Sub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${CrossAccountLogBucket.Arn}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nx"&gt;RoleARN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::GetAtt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LogStreamRole&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Arn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Allow the Firehose to write to S3
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;LogStreamRole&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::IAM::Role&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;RoleName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Sub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${serviceName}-${AWS::Region}-CrossAccountKinesisLogStream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;AssumeRolePolicyDocument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;Statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
          &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;Principal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firehose.amazonaws.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sts:AssumeRole&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;StringEquals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sts:ExternalId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::AccountId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}]&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;Policies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;PolicyDocument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s3:PutObject&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Sub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${CrossAccountLogBucket.Arn}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Sub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${CrossAccountLogBucket.Arn}/*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kinesis:GetRecords&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Sub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/${serviceName}-${AWS::Region}-Log-Sink*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create the CloudWatch Destination which can write to Firehose
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;AggregateLogEventsSubscriptionDestination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::Logs::Destination&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;DestinationName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Sub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${serviceName}-CrossAccountLogStream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;RoleArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::GetAtt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CloudWatchDelegatedRole&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Arn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;TargetArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Sub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${LogDeliveryStream.Arn}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;DestinationPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Sub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Principal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;logs:PutSubscriptionFilter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;arn:aws:logs:${AWS::Region}:${AWS::AccountId}:destination:${serviceName}-CrossAccountLogStream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;StringEquals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws:PrincipalOrgID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AWSOrgID&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}]&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;And enable it to write to Firehose
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;CloudWatchDelegatedRole&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS::IAM::Role&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;RoleName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Sub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${serviceName}-${AWS::Region}-CloudWatchCrossAccountAccess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;AssumeRolePolicyDocument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;Statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
          &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;Principal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Sub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;logs.${AWS::Region}.amazonaws.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sts:AssumeRole&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;StringEquals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws:PrincipalOrgID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AWSOrgID&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}]&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;Policies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;PolicyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FirehoseAccess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;PolicyDocument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firehose:PutRecord&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fn::Sub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${LogDeliveryStream.Arn}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
          &lt;span class="p"&gt;}]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last step here is create a subscription filter in the log source accounts on existing CloudWatch Log Groups, and you are done.&lt;/p&gt;




</description>
      <category>logging</category>
      <category>aws</category>
      <category>cloudwatch</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Step-up authorization</title>
      <dc:creator>Warren Parad</dc:creator>
      <pubDate>Fri, 08 Apr 2022 09:16:42 +0000</pubDate>
      <link>https://dev.to/authress/step-up-authorization-5fbf</link>
      <guid>https://dev.to/authress/step-up-authorization-5fbf</guid>
      <description>&lt;p&gt;Step up authorization is the process of converting a user’s auth from a base level to an elevated or privileged state. This is usually achieved by utilizing the user’s preconfigured two factor authentication methods.&lt;/p&gt;

&lt;p&gt;The result should be that the user is now able to access the more restricted resources on an account. This can be used to safeguard critical aspects of an account without inconveniencing the user by forcing 2FA from the beginning.&lt;/p&gt;

&lt;p&gt;However this is where trouble lies. And unfortunately, solutions are of the form:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forcing the user to do a full login again, using a different-method, tenant, connection, user identity pool-which has a 2FA enabled&lt;/li&gt;
&lt;li&gt;Storing the updated credential and losing the previous login&lt;/li&gt;
&lt;li&gt;User is stuck in elevated state&lt;/li&gt;
&lt;li&gt;Unable to segregate and explicitly identity which resources should be restricted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is called step-up authentication. This is actually a bad anti-pattern. The user’s identity hasn’t changed, and usually you aren’t concerned with user identity here.&lt;/p&gt;

&lt;p&gt;(There are some cases where we might require multiple forms of user identity that are desired for trust or user delegation, but all of these provide a better user experience by having remembered 2FA, location/ip address based validation, and user agent/browser token persistence during the initial login. All these aspects would be in the authentication domain and authentication should stop at user login.)&lt;/p&gt;

&lt;p&gt;Most of the time however, what we really want to do, is secure access to specific resources for the users benefit. Since we are talking about resource access, the appropriate term is &lt;strong&gt;step-up authorization&lt;/strong&gt; and not &lt;strong&gt;step-up authentication&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  This is an authorization and access problem, not an identity one.
&lt;/h4&gt;

&lt;p&gt;So using login or identity tools to do this is a mistake. It’s a different domain and so we should use the tools in the appropriate domain.&lt;/p&gt;

&lt;p&gt;To do step-up authorization, your IAM or authorization provider must support protected resource configuration to allow specifying when a user needs a step-up challenge. This also has the unique advantage of isolating security spheres and keeping the privileged access limited to the service/product/location that requested it, rather than applying it to the user identity across the board.&lt;/p&gt;

&lt;p&gt;In the case with user token changes, (i.e. the use of step-up authentication with re-login) you can’t restrict which resources need access. Further there is no way to reduce the access again without logging the user out or hacking the integration with the user’s identity provider to store multiple tokens, assuming the identity provider enables this at all. And thus having to store multiple tokens in every user client session, which becomes more of a problem when there are multiple apps involved or multiple user agents.&lt;/p&gt;

&lt;p&gt;Another way to look at the issue here is that, the user agent domain should never have to care about step-up access, the service API is the only one that should know this is required, and ask for it at the right time. But having step-up required, supported, and a first-class requirement of user-agents such as browsers incorrectly couples step-up flows with the UI the user interacts with. This fragments the implementation and segregates the understanding of the security features from where they are needed to where they should be opaque. Security is needed in the service, but the setup and management is forced into the UI. This is clearly incorrect. We want to have the knowledge of the security implementation to be directly aligned with the location where the step-up is requested.&lt;/p&gt;

&lt;p&gt;For the purposes of this implementation, we’ll walk through the architecture and integration with Authress to see how to easily implement step-up auth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step-Up Authorization configuration
&lt;/h3&gt;

&lt;p&gt;Authress breaks this down into a couple of different parts, we’ll go over each one of them here.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Specify the resource is protected
&lt;/h3&gt;

&lt;p&gt;The first step is to mark access to the specific resource as protected by step-up authorization. There are multiple ways to do this in practice, the preferred way is to specify in the access record that permission to access the resource should only happen with elevated token permissions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OEvG1w3h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/781/0%2A-4G7LkuaysZEqQ88.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OEvG1w3h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/781/0%2A-4G7LkuaysZEqQ88.png" alt="" width="781" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enabling this feature causes authorization checks to fail unless the user and token requesting the resource has been stepped-up.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Complete the normal login flow
&lt;/h3&gt;

&lt;p&gt;Users will navigate through your login as normal. As part of their account configuration, make sure to capture any mechanism you would like to use for the step-up flow. You’re probably already capturing their email or phone-number, however any available mechanism could be valid. It could even be the case to use Authress multi-signature request approval for this. Authress supports &lt;strong&gt;Access Requests&lt;/strong&gt; which generates a long running process to approve that request before granting the user access. In some cases, multiple entities must be involved in the step up request, and Authress provides a way to support it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Make the authorization request
&lt;/h3&gt;

&lt;p&gt;As usual, the user navigates through the resources in your platform. When they attempt to perform actions on your resources, you make the appropriate authorization checks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { AuthressClient } = require('authress-sdk');

const authressClient = new AuthressClient()

[GET('/v1/resources/{resourceId}')]
function getResource(request) {
  try {
    await authressClient.userPermissions.authorizeUser(userId, `resources/${resourceId}`, 'READ');
    _// Application route code_
    return OK;
  } catch (error) {
      if (error.code === 'StepUpAuthorizationRequired') {
          await issueStepUpChallenge(error.stepUpChallengeToken);
          return Forbidden;
      }
      return ServiceUnavailable;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;For language specific implementations of authorization checks,&lt;/em&gt; &lt;a href="https://authress.io/app/#/api"&gt;&lt;em&gt;see the available Authress SDKs&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Perform the step-up
&lt;/h3&gt;

&lt;p&gt;As part of the response from Authress on required step-up access includes the details to enforce the step-up challenge. Authress has the capability to generate a challenge code for future verification and verify a returning code for the user, this prevents just any valid client from approving the step-up. The recommended approach is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On a this failure, inspect the response, and check if a step-up challenge is necessary.&lt;/li&gt;
&lt;li&gt;In the case where a step up is required, issue the step-up challenge to the user, have the user complete their flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since this is all in the user authorization domain, the user doesn’t need to sign in again and no changes to the user identity nor delegation to an identity provider is necessary. Just issue the challenge and wait to hear back from the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Record to the step-up challenge result
&lt;/h3&gt;

&lt;p&gt;In the case that the user successfully completes your step-up challenge result, the question becomes&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How does the app link this elevated access to the existing user identity?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Since we want to restrict elevated permissions to authorization requests utilizing the user’s access token only, make a request to Authress’ user identity endpoint for the access token.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(This should be trivially obvious. If the user is logged in with more than one device, we only want one access token to be granted elevated permissions. This prevents accidentally granting all access tokens ever issued, that might still be active, elevated access. And additionally, the access token given granted step-up is fully controlled by your app/service/platform. This means the user can complete the challenge on their mobile device and elevated access will automatically happen in the browser session without ever needing to perform extra actions in either.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To record the step-up authorization challenge success for an access token, make the relevant call to the Authress API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { AuthressClient } = require('authress-sdk');

const authressClient = new AuthressClient()

function handleChallengeResult(stepUpChallengeToken) {
  _// Set the user's token as part of the request_
  const authorizationToken = request.headers.get('authorization');
  authressClient.setToken(authorizationToken);
  _// Use the stepUpChallengeToken from the error response to upgrade the state_
  await authressClient.userPermissions.stepUpAuthorization(stepUpChallengeToken);
  return OK;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Continue with the user resource action
&lt;/h3&gt;

&lt;p&gt;Once the step up challenge has been completed by the user, verified by the app service or platform using one of the user’s registered multi-factor devices, The user can be directed to make the same resource requests again. These resource requests will be successful, and the user can perform their desired actions.&lt;/p&gt;

&lt;p&gt;Once the actions have been completed. The app can optionally remove the step up authorization, by issuing another call to Authress, and revoking the permission. This also makes for a great flow in high-risk environments, where the user might not trust their device. This these situations, they can request that the step-up be always performed, and their associated access records can be modified to require that the user’s step up happens before access to the protected resources is given.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://authress.io/knowledge-base/step-up-authorization"&gt;&lt;em&gt;https://authress.io&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>microservices</category>
      <category>architecture</category>
      <category>authentication</category>
      <category>authorization</category>
    </item>
    <item>
      <title>Breaking up the monolith: Zero downtime migrations</title>
      <dc:creator>Warren Parad</dc:creator>
      <pubDate>Sun, 27 Feb 2022 10:25:50 +0000</pubDate>
      <link>https://dev.to/authress/breaking-up-the-monolith-zero-downtime-migrations-57ap</link>
      <guid>https://dev.to/authress/breaking-up-the-monolith-zero-downtime-migrations-57ap</guid>
      <description>&lt;p&gt;It’s pretty common in monolith architectures to have to handle migrations. But this isn’t the only place. Microservices also frequently will have the need for database migrations. Unlike in monoliths where zero-downtime is usually impossible, microservices &lt;strong&gt;enable&lt;/strong&gt; the capability to perform migrations with no downtime whatsoever.&lt;/p&gt;

&lt;p&gt;Opponents of microservices, you know who you are, might say “oh now you need to learn to do this”. You don’t, you can continue with your app, which goes offline on the weekends and display a prominent banner that “our development team has been outsourced so we can’t do this simple activity without impacting our users.”&lt;/p&gt;

&lt;h3&gt;
  
  
  The fundamental need
&lt;/h3&gt;

&lt;p&gt;Even in the most simple cases, you might find yourself in a situation where a database migration is important. You’ve labeled a critical column poorly, you are trying to reduce your DB size, or most probably, your primary index is wrong. It is unfortunate, but it does happen. The recommended approach is &lt;strong&gt;do nothing&lt;/strong&gt;. That’s right, first evaluate how much of a problem this is. Sure it has been working okay until now, and it will continue to get worse, but how much worse? Are we talking about 0.001% of calls are problematic, or even 100% are but only suffer a performance degradation of an extra 1ms?&lt;/p&gt;

&lt;p&gt;Before performing any work, always evaluated the value of doing so. While migrations are easy, it doesn’t mean they should be done.&lt;/p&gt;

&lt;h3&gt;
  
  
  The setup of a migration
&lt;/h3&gt;

&lt;p&gt;So you’ve decided that the migration should be done. No problem, time to make it happen. Migrations always work the same way, and they are easy, once you know what to do.&lt;/p&gt;

&lt;p&gt;(Caveat: This works reliably for database sizes up to a few Terabytes. Larger than that it will start to take some real time and put a real load on your migration infrastructure. In those cases you might think about a complimentary approach to port the data from one location to another in addition to the pattern.)&lt;/p&gt;

&lt;p&gt;Here is the pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploy a new replica database — You should have two versions of your DB running at all times, no exceptions. You cannot do this in one step and it must be done over time. It is always a trade-off for migrations, either take downtime and do it quickly or take no downtime and do it slowly. Zero-downtime is a slow and careful approach. One important part is to have a field called &lt;strong&gt;lastUpdated&lt;/strong&gt; to indicated the most recent &lt;strong&gt;write&lt;/strong&gt;. If you don’t already have something like this in your object, it’s really valuable in itself, but now is the required time to add it.&lt;/li&gt;
&lt;li&gt;Duplicate the write logic — Duplicate all logic to write to both the current location and the new location. There are a bunch of ways to do this, the easiest is duplicate the code. The hardest is setup a post processing of your DB to read from the old location and write to the new location. (If you were using AWS DynamoDB this would be a &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.html"&gt;Simple Stream&lt;/a&gt;.) — Make sure that the &lt;strong&gt;lastUpdated&lt;/strong&gt; property is being set. This probably should also be used to ensure that data that is written has a lastUpdated that is later than the current value.&lt;/li&gt;
&lt;li&gt;Replicate existing data — At this point all &lt;strong&gt;New&lt;/strong&gt; data is going to be in both places. Any data row/object/item that gets written will be exactly the same, the only problem is any old data, this will live in the legacy system but not the new one. The next step is to copy all the existing data to your new database. When these updates happen they must check that their value is greater than the previous &lt;strong&gt;lastUpdated.&lt;/strong&gt; This check ensures that a new write isn’t overwritten by old data. If you don’t do this, it will be impossible to ensure a complete and correct migration.&lt;/li&gt;
&lt;li&gt;Validate the migration — Now is the time to check that the data migration worked correctly, run two &lt;strong&gt;read&lt;/strong&gt; operations on both tables and validate that every row is the same, you can ignore an row that was updated &lt;strong&gt;after&lt;/strong&gt; the validation started running (because we know that they’ll be the same). The check is something like — Read from table 1, check when it was last updated (if possible), then if needs to be validated, read from table 2, is the lastUpdated after “migration start time” if yes, ignore, if the same ignore, if they are different, investigate.&lt;/li&gt;
&lt;li&gt;Start using new table — Change all the read operations to using the new table. Since all the data in the tables are now consistent, it doesn’t matter which one we read from. So switch to reading from table 2.&lt;/li&gt;
&lt;li&gt;Cleanup — Change the “write to table 1 logic” to “write to table 2”, and delete the “duplicate data from table 1 to table 2”. We don’t need the duplication anymore, and since we don’t need table 1 anymore, we can remove the logic to write to it.&lt;/li&gt;
&lt;li&gt;Delete Legacy database — Now we can delete table 1, it isn’t necessary anymore, and that’s the end of the migration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is however a race condition here that you need to be aware of. When we are in Step 5 (start using the new table):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writing to table 1&lt;/li&gt;
&lt;li&gt;Duplicating to table 2&lt;/li&gt;
&lt;li&gt;Reading from table 1 — About to switch this to table 2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If there is a delay between the writing to table 1 and the duplicating to table 2, you could be reading stale data. In 99% of cases this doesn’t matter, in some areas this could be a problem, so you need to make sure that the logic that does the duplication of this data is completed before doing the switch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await write1();
await duplicateTo2();
await read2();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s all you need to ensure, this works even in distributed systems and when using transactions.&lt;/p&gt;




</description>
      <category>architecture</category>
      <category>microservices</category>
      <category>migration</category>
      <category>database</category>
    </item>
    <item>
      <title>Adding Custom Domains to your SaaS</title>
      <dc:creator>Warren Parad</dc:creator>
      <pubDate>Fri, 25 Feb 2022 11:17:26 +0000</pubDate>
      <link>https://dev.to/authress/adding-custom-domains-to-your-saas-4hci</link>
      <guid>https://dev.to/authress/adding-custom-domains-to-your-saas-4hci</guid>
      <description>&lt;p&gt;You're building out a SaaS solution and realize for one reason or another supporting &lt;strong&gt;custom domains&lt;/strong&gt; for your customers is a must. There are some products out there that can do this for you in some way, but they either cost a lot, are a huge maintenance burden or aren't scalable. Here, we'll walk through using AWS to add custom domains to your solution.&lt;/p&gt;




&lt;h2&gt;
  
  
  What are Custom Domains?
&lt;/h2&gt;

&lt;p&gt;First, let's discuss what a &lt;strong&gt;custom domain&lt;/strong&gt; actually is. A custom domain, let's your customer brand your API, UI, service, or product with their domain on top of your service. This provides a huge amount of value for both parties. For customers they can provide your service to their third parties or hide the implementation detail of what they are using. No one wants to expose their dependencies to customers, it's unnecessary and dangerous.&lt;/p&gt;

&lt;p&gt;For example if you have a product @ &lt;a href="https://product-service.com" rel="noopener noreferrer"&gt;https://product-service.com&lt;/a&gt; and offer custom domains for your customers, they may look like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://customer-A.product-service.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://02c77286.product-service.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This might be fine in a lot of situations, you can give the customer this identifier or url and call it a day. You might even have some logic which would associate this with their user/account to automatically redirect them. That's all good, but in the first case, let's say you want to include a &lt;strong&gt;Support Portal&lt;/strong&gt; as part of your &lt;strong&gt;Product Service&lt;/strong&gt;, you don't want to redirect your customers to &lt;code&gt;https://customer-A.third-party-website.com&lt;/code&gt;, you want them to stay on the same domain.&lt;/p&gt;

&lt;p&gt;As a concrete example, when you sign up for our product you get an account ID that looks like &lt;code&gt;bwdlb5r89jwhy232&lt;/code&gt;, so the urls look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm0qxeen8b0cwfgo5hocd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm0qxeen8b0cwfgo5hocd.png" alt="Image description" width="777" height="264"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  So why are we doing this again?
&lt;/h2&gt;

&lt;p&gt;Just like the support portal option, custom domains have two other important uses.&lt;/p&gt;

&lt;h4&gt;
  
  
  Internal benefit
&lt;/h4&gt;

&lt;p&gt;One example is your benefit. We'll use our product &lt;a href="https://authress.io" rel="noopener noreferrer"&gt;Authress&lt;/a&gt; as an example. Authress provides an API for access control. That means every customer has their user identities, resources, roles, and permissions managed by the service. Since Authress optimizes permission queries as well as user credential security and caching, segregating each customer to their own subdomain has a lot of value for us. Authress charges by API call, that means tracking of these calls is critical, using the subdomain is a great way to separate calls.&lt;/p&gt;

&lt;p&gt;When the service allocates a subdomain to an account, the subdomain matches the accountId:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://accountId-A1.api.authress.io&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And while this is easy for developers to use in a service, it isn't pretty. So we provide the capability to mask this domain with one the customer chooses.&lt;/p&gt;

&lt;h4&gt;
  
  
  Security implications
&lt;/h4&gt;

&lt;p&gt;Security is improved via:&lt;/p&gt;

&lt;p&gt;A) There are lots of restrictions that exist on a per domain basis, CORS/Cookies are one of them. Allowing your users to set their custom domain for your product to match their domain, allows them to consume these resources in a safe way. Having different domains may tempt you to store some things that you shouldn't.&lt;/p&gt;

&lt;p&gt;B) User SSO and authentication. For SSO, every business customer will want to have their own login provider (IdP) utilized. That means Account 1 uses IdP A, Account 2 uses IdP B, and so on. There is no way to know before a user logs in which IdP to use, so we need to guess. We could guess by forcing the user to enter their email, and then using the &lt;code&gt;domain&lt;/code&gt; of the email.&lt;/p&gt;

&lt;p&gt;This is a terrible solution. Instead, let the customer use a custom domain on top of your subdomain to configure their IdP. Then you can just look at the domain to determine which IdP to you. (Spoiler: this is exactly what Authress does to automatically support SSO for B2B products, you don't need to do any extra work, if implemented through Authress connections).&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting up a custom domain
&lt;/h2&gt;

&lt;p&gt;So you want to let your customers, tell you which domain they want and then you map&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://custom.domain.com =&amp;gt; https://customer-A.product-service.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is mostly a DNS record, and you are done. All they have to do is add &lt;strong&gt;CNAME&lt;/strong&gt; added to their Domain Registrar's hosted zone.&lt;/p&gt;

&lt;p&gt;Sounds easy doesn't it? So why do all these companies try to charge you a ton for this?&lt;/p&gt;




&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;While the CNAME works out of the box, the real problem comes with TLS or HTTPS. In order for that CNAME to work and for their users to correctly have encrypted traffic, &lt;strong&gt;YOUR&lt;/strong&gt; service needs to serve a TLS certificate that matches &lt;strong&gt;THEIR&lt;/strong&gt; domain.&lt;/p&gt;

&lt;p&gt;If this was your domain, you would add a DNS verification to your Hosted Zone, and your cert would work. Or You could &lt;a href="https://aws.amazon.com/certificate-manager/" rel="noopener noreferrer"&gt;AWS Certificate Manager&lt;/a&gt; and this would work without even any code.&lt;/p&gt;

&lt;p&gt;But you don't own this domain, which means somehow you need them to give you a cert, and this cert has to be renewable, that means every 3 months~1 year you would need a new cert...For every customer...&lt;/p&gt;

&lt;p&gt;This also means maintaining a database of these encrypted certs and safe-guarding them, because if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lose them, then all your customers can no longer access your site&lt;/li&gt;
&lt;li&gt;expose access to them, then anyone can impersonate your customers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both of these are really bad.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;(A small aside: If you actually wanted to pay for this, there is a discussion about all the different options available on &lt;a href="https://www.indiehackers.com/post/how-to-implement-custom-domains-support-for-my-saas-product-214828c6ca" rel="noopener noreferrer"&gt;IndieHackers&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;As I mentioned there are tons of solutions to this out in the world, but none of them are great, they are costly to maintain, and worse, if there is something that goes wrong, it will go really wrong. So here I'm going to outline how you can do with this AWS Certificate (ACM) and AWS CloudFront (CF) and not have to worry about anything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Set up your service
&lt;/h3&gt;

&lt;p&gt;Run your service like you would, wherever you would, this could be AWS Lambda or ECS (or if you are a masochist--EC2, or just want to burn a lot of money for no reason--EKS).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Support wildcard processing
&lt;/h3&gt;

&lt;p&gt;Your service needs to work with wildcards or genericly specified domains. This means that &lt;code&gt;https://*.product-service.com&lt;/code&gt; needs to be handled correctly by your stack. For APIGW, this means adding a custom domain for AWS APIGW that includes the &lt;code&gt;*&lt;/code&gt;. For a static site running in S3, however, you don't need any extra magic.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Enable custom domain requests
&lt;/h3&gt;

&lt;p&gt;Let your customer request "I want to setup a custom domain, my domain is &lt;code&gt;example.com&lt;/code&gt;". Generally speaking, require them to use a subdomain, because you likely aren't hosting their main website. For Authress, we require subdomains lik &lt;code&gt;login.example.com&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Deploy Customer Account specific resources
&lt;/h3&gt;

&lt;p&gt;To do this we'll deploy a new CloudFront and a new Certificate, both in the US-EAST-1 region (this is important). Both resources should request resources with the custom domain specified.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new CloudFront Distribution with &lt;strong&gt;No&lt;/strong&gt; alternate domain name set. (If you try to use the cert before it is validated it will fail, so don't do this). This distribution should point to your existing infrastructure, and have custom headers set for the specific customer accountId/subdomain. The CF distribution will have a domain like:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dmv3o3npogaoea.cloudfront.net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Request a new ACM certificate with the custom domain and get back the DNS validation options, they look like this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name: _85dd6f5e25429205f5ffa77.example.com.
Value: _caee56563101.aocomg.acm-validations.aws.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Mask these values so you can make updates later, by creating two CNAME records in your hosted zone:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CNAME: validation-customer-A.product-service.com
Value: _caee56563101.aocomg.acm-validations.aws (value from above)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AND&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name: customer-A.product-service.com
Value: dmv3o3npogaoea.cloudfront.net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Return these two sets of DNS CNAME values:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name: _85dd6f5e25429205f5ffa77.example.com (Name from above)
Value: validation-customer-A.product-service.com (our custom name)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AND&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name: example.com (their custom domain)
Value: customer-A.product-service.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Wait for your customer to add these two CNAME records to their domain hosted zone.
&lt;/h3&gt;

&lt;p&gt;Concretely, from our example it would looks like this:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fks79wduxhuobnpdfs1pa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fks79wduxhuobnpdfs1pa.png" alt="Image description" width="790" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8v2fyo2e2o7dar3yp5eg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8v2fyo2e2o7dar3yp5eg.png" alt="Image description" width="800" height="187"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  8. Once verified update your CloudFront distribution alternate domain name and certificate that was just verified.
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faytwbu4156hzgbmwkn90.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faytwbu4156hzgbmwkn90.png" alt="Image description" width="608" height="77"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq9aplk34peqtva73rky2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq9aplk34peqtva73rky2.png" alt="Image description" width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;Now you should be successfully running the CloudFront distribution as a TLS proxy for your service, website, app, etc... It will allow the white labeling that your customers need and you don't need to pay a fortunate to managing anything. ACM and ACF are free, and all the traffic you would pay for anyway just gets migrated to a new CF.&lt;/p&gt;

&lt;p&gt;Even better? you can attach protections to your infrastructure to monitor the usage of these proxies to know how they are being used. And further, all the tools exist at the CF level for rate limiting, DDoS protection, real-time logging, custom assets, and the list goes on.&lt;/p&gt;




&lt;p&gt;Come join our &lt;a href="https://authress.io/community/" rel="noopener noreferrer"&gt;Community&lt;/a&gt; and discuss this and other security related topics!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://authress.io/community" rel="noopener noreferrer"&gt;&lt;br&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7kfcgr5kmkwi1rxu471k.png" alt="Join the community" width="348" height="66"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>aws</category>
      <category>saas</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
