<?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: Eugene Zimin</title>
    <description>The latest articles on DEV Community by Eugene Zimin (@eugene-zimin).</description>
    <link>https://dev.to/eugene-zimin</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%2Fuser%2Fprofile_image%2F1237860%2F9be1289e-94c0-47ce-b8c9-b7385ee2ab47.png</url>
      <title>DEV Community: Eugene Zimin</title>
      <link>https://dev.to/eugene-zimin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eugene-zimin"/>
    <language>en</language>
    <item>
      <title>Drawing the Blueprint: Flowchart, Functional Diagram, and Sequence Diagram</title>
      <dc:creator>Eugene Zimin</dc:creator>
      <pubDate>Sat, 23 May 2026 20:45:57 +0000</pubDate>
      <link>https://dev.to/eugene-zimin/drawing-the-blueprint-flowchart-functional-diagram-and-sequence-diagram-537c</link>
      <guid>https://dev.to/eugene-zimin/drawing-the-blueprint-flowchart-functional-diagram-and-sequence-diagram-537c</guid>
      <description>&lt;h1&gt;
  
  
  Lesson 3 of &lt;em&gt;Build a Twitter Clone&lt;/em&gt; - A Practical Guide to Software Modelling
&lt;/h1&gt;

&lt;p&gt;In &lt;a href="https://dev.to/eugene-zimin/the-blueprint-beneath-the-blueprint-designing-data-model-and-choosing-its-database-3bhl"&gt;Lesson 2&lt;/a&gt;, we defined what &lt;code&gt;Bird&lt;/code&gt; must &lt;em&gt;remember&lt;/em&gt;: two databases, tables within, every field traced back to a use case. Now it's time to draw what &lt;code&gt;Bird&lt;/code&gt; must &lt;em&gt;do&lt;/em&gt;. Using &lt;code&gt;"Post a message"&lt;/code&gt; as our single worked example, we construct all three diagrams in sequence - first a flowchart that traces the user action step by step, then a functional diagram that reads the required components directly off that flowchart, then a sequence diagram that shows exactly how those components talk. By the end, the use case that started as a sentence on a sticky note has a complete, traceable blueprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction - The Foundation Is Ready
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/eugene-zimin/the-blueprint-beneath-the-blueprint-designing-data-model-and-choosing-its-database-3bhl"&gt;Lesson 2&lt;/a&gt; ended with a promise, and it's worth quoting precisely:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"That's where Lesson 3 picks up: with the data model as the foundation, we draw all three diagrams."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That moment has arrived. But it's worth pausing for a second to notice what's different now compared to where we started.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/eugene-zimin/from-idea-to-blueprint-turning-a-vague-app-concept-into-something-you-can-actually-build-1a60"&gt;Lesson 1&lt;/a&gt;, we introduced the idea of three diagrams - a flowchart, a functional diagram, and a sequence diagram - as three lenses on a single system. We explained the mental model, argued for the order, and left it there. No diagrams were actually drawn, and that was deliberate: a diagram drawn before the project is bounded and the data understood is a diagram you'll redraw.&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%2Fafgc5ff3q079abfk9ild.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%2Fafgc5ff3q079abfk9ild.png" alt="Figure 1. The Foundation Is Ready" width="799" height="384"&gt;&lt;/a&gt;Figure 1. The Foundation Is Ready&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/eugene-zimin/the-blueprint-beneath-the-blueprint-designing-data-model-and-choosing-its-database-3bhl"&gt;Lesson 2&lt;/a&gt;, we did the bounding. We read &lt;code&gt;Bird&lt;/code&gt;'s three use cases for data clues, named two entities (&lt;code&gt;User&lt;/code&gt; and &lt;code&gt;Message&lt;/code&gt;), defined five tables across two databases (&lt;code&gt;ums&lt;/code&gt; and &lt;code&gt;twitter&lt;/code&gt;), and established the relationships that tie them together. The schema is real now: a &lt;code&gt;messages&lt;/code&gt; row has a specific shape - an &lt;code&gt;id&lt;/code&gt;, a &lt;code&gt;content&lt;/code&gt; field capped at 280 characters, an &lt;code&gt;author_id&lt;/code&gt;pointing to a row in &lt;code&gt;ums.users&lt;/code&gt;, and a &lt;code&gt;created_at&lt;/code&gt; timestamp. The &lt;code&gt;users&lt;/code&gt; row has a shape too. Every "save the message" step in every diagram we draw will refer to something concrete.&lt;/p&gt;

&lt;p&gt;That concreteness is what changes everything. A diagram drawn on top of a real data model isn't a sketch - it's a specification. The boxes mean something. The arrows carry named fields. When a step says &lt;em&gt;"validate the message,"&lt;/em&gt; we already know what the validation is checking (which is called validation criteria): a non-empty &lt;code&gt;content&lt;/code&gt; string, no longer than 280 characters, attached to an authenticated &lt;code&gt;author_id&lt;/code&gt;. Nothing is left as a placeholder.&lt;/p&gt;

&lt;p&gt;This lesson draws all three diagrams. We'll work in order - behaviour first, then the structure that behaviour implies, then the detailed conversation inside that structure - for the same reason we always work in that order: each diagram is the input to the next, and the input has to exist before the output can be correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quick Reminder: The Three-Lens Progression
&lt;/h2&gt;

&lt;p&gt;Before we pick up the pen, one short bridge back to &lt;a href="https://dev.to/eugene-zimin/from-idea-to-blueprint-turning-a-vague-app-concept-into-something-you-can-actually-build-1a60"&gt;Lesson 1&lt;/a&gt; - not a full re-teach, just enough to make sure the scaffold is standing.&lt;/p&gt;

&lt;p&gt;The core idea is this: any software system can be understood from three distinct angles, and each angle demands a different kind of diagram.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lens&lt;/th&gt;
&lt;th&gt;The question it answers&lt;/th&gt;
&lt;th&gt;The diagram&lt;/th&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Behaviour&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What should the app &lt;em&gt;do&lt;/em&gt;?&lt;/td&gt;
&lt;td&gt;Flowchart&lt;/td&gt;
&lt;td&gt;Steps, decisions, and outcomes - the system as a &lt;em&gt;process&lt;/em&gt; unfolding in time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Structure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What &lt;em&gt;parts&lt;/em&gt; must exist to make that behaviour happen?&lt;/td&gt;
&lt;td&gt;Functional diagram&lt;/td&gt;
&lt;td&gt;Components and their connections - the system as a &lt;em&gt;thing made of pieces&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Interaction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How do those parts &lt;em&gt;talk&lt;/em&gt; to each other at runtime?&lt;/td&gt;
&lt;td&gt;Sequence diagram&lt;/td&gt;
&lt;td&gt;Messages exchanged between components - the system as a &lt;em&gt;conversation&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these is more correct than the others. A flowchart can't tell you what components to build. A functional diagram can't tell you what happens when a user submits an empty message. A sequence diagram can't tell you whether you've correctly scoped the feature. They are &lt;strong&gt;complementary&lt;/strong&gt;, not competing - and you need all three because no single one is complete.&lt;/p&gt;

&lt;p&gt;The critical habit is the &lt;strong&gt;order&lt;/strong&gt;. You move behaviour → structure → interaction, and each step is derived from the one before:&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%2Fj1cqn5nl8mqxij3nzfje.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%2Fj1cqn5nl8mqxij3nzfje.png" alt="Figure 2. The three-lens progression - each diagram derives from the one before" width="800" height="126"&gt;&lt;/a&gt;Figure 2. The three-lens progression - each diagram derives from the one before&lt;/p&gt;

&lt;p&gt;The reason the order is fixed: you can't know what components to build until you know what the system must do. And you can't describe the conversation between components until you know which components exist. Behaviour is the requirement; structure serves it; interaction lives inside structure.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;An analogy for the progression.&lt;/strong&gt; Imagine designing a kitchen. First you decide what the kitchen must &lt;em&gt;do&lt;/em&gt;- prepare meals, store food, clean dishes. That's behaviour. Then you ask what &lt;em&gt;parts&lt;/em&gt; must exist to support each of those tasks: a cooktop, a refrigerator, a sink. That's structure, falling out of behaviour. Finally, you trace the specific workflow - the chef moves from the refrigerator to the prep surface to the cooktop, in that order, passing ingredients along. That's interaction, living inside the structure. You couldn't have designed the workflow without knowing the layout. You couldn't have designed the layout without knowing the tasks. The order is the method.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Lens 1: Behaviour - Drawing the Flowchart
&lt;/h2&gt;

&lt;p&gt;A flowchart answers one question: &lt;strong&gt;what does the system do, step by step?&lt;/strong&gt; Not what parts it has. Not which component calls which. Just the sequence of actions and decisions that carry a user from "I want to post a message" to "it's done" - or to an error, if something goes wrong along the way.&lt;/p&gt;

&lt;p&gt;That makes the flowchart the right place to start. It captures the requirement in its purest form, before any implementation choices have been made.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building it step by step
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1 - Name the start and end points.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The flow begins the moment the user presses "Post" - that is, when the system receives a submission. It ends either with a new row in &lt;code&gt;twitter.messages&lt;/code&gt; and a confirmation to the user, or with an error message and no row written. Name those endpoints first, before filling in anything in between.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 - List the actions in order.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Walk the happy path first - what happens when everything goes right? For "Post a message," that sequence is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Submit a message&lt;/li&gt;
&lt;li&gt;Pass the authentication check - is the user logged in?&lt;/li&gt;
&lt;li&gt;Pass the permissions check - is the user allowed to post?&lt;/li&gt;
&lt;li&gt;Pass content validation - is the message valid?&lt;/li&gt;
&lt;li&gt;Save the message to the database&lt;/li&gt;
&lt;li&gt;Confirm success to the user&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Six steps. Two are actions, three are decision gates, one is a confirmation. That's a healthy breakdown - the logic is explicit without becoming an implementation manual.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 - Add the decision points.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three decision diamonds gate the happy path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Is the user logged in?&lt;/strong&gt; If no → redirect to the login page. After a successful login the user is returned to the submission step - a loop, not a dead end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is the user allowed to post?&lt;/strong&gt; Not every account has posting rights - a read-only or suspended role is blocked here. If no → shared Error node → END.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is the message valid?&lt;/strong&gt; (Non-empty? Within 280 characters?) If invalid → shared Error node → END.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice that the two error branches converge on a single &lt;code&gt;Error&lt;/code&gt; node before reaching &lt;code&gt;END&lt;/code&gt;. This is an intentional design choice in the diagram: the system's response to a permissions failure and a content failure is the same kind of thing - an error returned to the user. Merging them keeps the diagram clean and makes that equivalence visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 - Connect and label.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arrange the steps top to bottom, connect them with arrows, and label every arrow leaving a decision diamond. The labels on the branches ("yes / no", or more descriptively "valid / invalid") are what make the diagram readable at a glance.&lt;/p&gt;

&lt;h3&gt;
  
  
  The flowchart
&lt;/h3&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%2Fndj15078ai1yo0txid9i.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%2Fndj15078ai1yo0txid9i.png" alt="Figure 3. Flowchart for " width="800" height="1225"&gt;&lt;/a&gt; Figure 3. Flowchart for "Post a message"&lt;/p&gt;

&lt;h3&gt;
  
  
  What this diagram tells us - and what it doesn't
&lt;/h3&gt;

&lt;p&gt;The flowchart is intentionally silent on &lt;em&gt;how&lt;/em&gt; each step is implemented. It doesn't say which component checks authentication, or what library validates the string length, or whether the database write is synchronous. Those details don't belong here. What the flowchart does - and does well - is make the &lt;strong&gt;logic&lt;/strong&gt; visible: the decisions, the branches, the outcomes. That's the entire job of Lens 1.&lt;/p&gt;

&lt;p&gt;Notice that the data model already sharpens the language. The "Save message" step doesn't just say &lt;em&gt;"save the message"&lt;/em&gt;- it names the fields: &lt;code&gt;author_id&lt;/code&gt;, &lt;code&gt;content&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt;, writing into &lt;code&gt;twitter.messages&lt;/code&gt;. That specificity comes directly from &lt;a href="https://dev.to/eugene-zimin/the-blueprint-beneath-the-blueprint-designing-data-model-and-choosing-its-database-3bhl"&gt;Lesson 2&lt;/a&gt;. The flowchart is still high-level, but it's no longer vague.&lt;/p&gt;

&lt;p&gt;With the behaviour fully drawn, the next question asks itself: &lt;strong&gt;what parts must exist to make each of these steps happen?&lt;/strong&gt; That's Lens 2 - and the answer comes directly from reading this flowchart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lens 2: Structure - Reading the Functional Diagram off the Flowchart
&lt;/h2&gt;

&lt;p&gt;The flowchart is done. We know what "Post a message" must do - the steps, the decisions, the branches. Now a different question takes over: &lt;strong&gt;what parts must exist inside the system to make each of those steps happen?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where most beginners get into trouble. They open a new diagram and start inventing components - a generic &lt;code&gt;AuthService&lt;/code&gt;, an &lt;code&gt;APIGateway&lt;/code&gt;, a &lt;code&gt;DatabaseLayer&lt;/code&gt; - based on instinct or prior experience. The result is a diagram that reflects what the developer already knew, not what the use case actually requires.&lt;/p&gt;

&lt;p&gt;There is a better method. The functional diagram doesn't need to be invented. It can be &lt;strong&gt;read directly off the flowchart&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The method: scan every step, ask one question
&lt;/h3&gt;

&lt;p&gt;For each step in the flowchart, ask: &lt;em&gt;what component must own this responsibility?&lt;/em&gt; The answer names a block. Do that for every step, merge the ones that belong together, and the structure falls out of the behaviour.&lt;/p&gt;

&lt;p&gt;Let's walk through the flowchart step by step.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flowchart step&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;User submits message&lt;/td&gt;
&lt;td&gt;Sends the HTTP request&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Frontend Client&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is the user logged in?&lt;/td&gt;
&lt;td&gt;Identifies who is making the request&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Twitter Service&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redirect to login&lt;/td&gt;
&lt;td&gt;Returns the login page&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Frontend Client&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is the user allowed to post?&lt;/td&gt;
&lt;td&gt;Fetches the user record and checks for the &lt;code&gt;PRODUCER&lt;/code&gt;role&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Twitter Service&lt;/strong&gt; → &lt;strong&gt;UMS Service&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is the message valid?&lt;/td&gt;
&lt;td&gt;Enforces content rules (non-empty, ≤ 280 chars)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Twitter Service&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Save message&lt;/td&gt;
&lt;td&gt;Writes the row to &lt;code&gt;twitter.messages&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Twitter Service&lt;/strong&gt; → &lt;strong&gt;MySQL Database&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Confirm success&lt;/td&gt;
&lt;td&gt;Returns &lt;code&gt;201&lt;/code&gt; with the created message&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Twitter Service&lt;/strong&gt; → &lt;strong&gt;Frontend Client&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Return error&lt;/td&gt;
&lt;td&gt;Returns &lt;code&gt;403 Forbidden&lt;/code&gt; to the client&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Twitter Service&lt;/strong&gt; → &lt;strong&gt;Frontend Client&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things stand out immediately. First, the &lt;strong&gt;Twitter Service&lt;/strong&gt; carries the most weight: it receives the request, performs content validation, and orchestrates the database write. But it doesn't hold user data - that lives in UMS. So for the permission check, the Twitter Service calls the &lt;strong&gt;UMS Service&lt;/strong&gt; via an internal HTTP request, retrieves the user's roles, and decides whether to proceed. This cross-service call is the architectural heart of the feature.&lt;/p&gt;

&lt;p&gt;Second, there is no separate &lt;code&gt;API Gateway&lt;/code&gt; or &lt;code&gt;Auth Service&lt;/code&gt; in &lt;code&gt;Bird&lt;/code&gt;. The Frontend Client talks directly to the Twitter Service on port &lt;code&gt;9001&lt;/code&gt;. Role checking is a responsibility of the Twitter Service itself, delegated to UMS on demand - not a standalone component sitting in between.&lt;/p&gt;

&lt;p&gt;This gives us four components: the &lt;strong&gt;Frontend Client&lt;/strong&gt;, the &lt;strong&gt;Twitter Service&lt;/strong&gt;, the &lt;strong&gt;UMS Service&lt;/strong&gt;, and &lt;strong&gt;MySQL Database&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The functional diagram
&lt;/h3&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%2Fl813m8f32djv1op3akk5.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%2Fl813m8f32djv1op3akk5.png" alt="Figure 4. Functional diagram for  raw `Bird` endraw " width="800" height="1012"&gt;&lt;/a&gt; Figure 4. Functional diagram for &lt;code&gt;Bird&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The diagram reads as a set of named blocks connected by labelled arrows. The arrows carry the nature of the relationship - not the step-by-step logic (that's the flowchart's job) but the structural dependency: the Frontend Client sends requests to the Twitter Service; the Twitter Service calls UMS to validate the user; the Twitter Service reads and writes MySQL directly. Each arrow represents a real, named communication channel in the codebase - HTTP on &lt;code&gt;:9001&lt;/code&gt; for the UI-to-service call, &lt;code&gt;WebClient&lt;/code&gt; for the service-to-service call, JDBC for the database connection.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this diagram is - and isn't
&lt;/h3&gt;

&lt;p&gt;A functional diagram is a &lt;strong&gt;map of components&lt;/strong&gt;, not a map of steps. It answers "what pieces exist and how are they connected?" - not "what happens first?" The flowchart and the functional diagram are not redundant; they answer different questions and each is incomplete without the other.&lt;/p&gt;

&lt;p&gt;Notice also what the functional diagram deliberately omits: there are no arrows labelled with field names, no decision branches, no error paths, no sequence numbers. Those details live in the sequence diagram. Here, the goal is structural clarity - a reader should be able to see at a glance what &lt;code&gt;Bird&lt;/code&gt; is made of and how the pieces relate.&lt;/p&gt;

&lt;p&gt;With the components named and connected, one question remains: &lt;strong&gt;how exactly do they talk to each other during "Post a message"?&lt;/strong&gt; That's the sequence diagram - and now that we know which components exist, we can finally draw it precisely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lens 3: Interaction - The Sequence Diagram
&lt;/h2&gt;

&lt;p&gt;The flowchart told us &lt;em&gt;what&lt;/em&gt; "Post a message" does. The functional diagram told us &lt;em&gt;what components&lt;/em&gt; exist to do it. The sequence diagram answers the last question: &lt;strong&gt;what do those components say to each other, in what order, when the feature actually runs?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where the previous two diagrams earn their investment. Every component named in the functional diagram becomes a lifeline. Every step in the flowchart becomes a message on one of those lifelines. Nothing is invented - it's assembled from what we already know.&lt;/p&gt;

&lt;h3&gt;
  
  
  From components to lifelines
&lt;/h3&gt;

&lt;p&gt;A sequence diagram is built around &lt;strong&gt;lifelines&lt;/strong&gt; - vertical columns, one per component, representing each participant in the interaction. Reading the functional diagram directly, our lifelines are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend Client&lt;/strong&gt; - the browser, initiating the request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MessageController&lt;/strong&gt; - the entry point inside the Twitter Service, receiving the HTTP call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MessagesService&lt;/strong&gt; - the orchestrator: calls UMS, checks roles, triggers the database write&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UMSConnector&lt;/strong&gt; - the thin HTTP client that talks to UMS via &lt;code&gt;WebClient&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UMS Service&lt;/strong&gt; - the external service that owns user identity and roles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JdbcMessageRepository&lt;/strong&gt; - the DAO that executes the SQL insert&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MySQL&lt;/strong&gt; - the database that persists the row&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reading the flow
&lt;/h3&gt;

&lt;p&gt;The messages between lifelines follow the exact path the codebase executes. Let's trace it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;Frontend Client&lt;/strong&gt; sends &lt;code&gt;POST /messages/message&lt;/code&gt; with &lt;code&gt;{ author, content }&lt;/code&gt; to the &lt;strong&gt;MessageController&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MessageController&lt;/strong&gt; deserializes the body into a &lt;code&gt;Message&lt;/code&gt; DTO and delegates to &lt;strong&gt;MessagesService&lt;/strong&gt; via &lt;code&gt;createMessage(message)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MessagesService&lt;/strong&gt; needs to verify the author - it doesn't hold user data, so it calls &lt;strong&gt;UMSConnector&lt;/strong&gt; with &lt;code&gt;retrieveUmsData(/users/user/{id})&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UMSConnector&lt;/strong&gt; fires a non-blocking &lt;code&gt;WebClient GET&lt;/code&gt; to the &lt;strong&gt;UMS Service&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UMS Service&lt;/strong&gt; returns a &lt;code&gt;UserDto&lt;/code&gt; containing the user's roles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MessagesService&lt;/strong&gt; unpacks the response via &lt;code&gt;HttpResponseExtractor&lt;/code&gt; and inspects the &lt;code&gt;Roles&lt;/code&gt;. If the user is not a &lt;code&gt;PRODUCER&lt;/code&gt; - the flow terminates here with a &lt;code&gt;403 Forbidden&lt;/code&gt; back to the client.&lt;/li&gt;
&lt;li&gt;If the role check passes, &lt;strong&gt;MessagesService&lt;/strong&gt; calls &lt;code&gt;createMessage(message)&lt;/code&gt; on &lt;strong&gt;JdbcMessageRepository&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JdbcMessageRepository&lt;/strong&gt; executes &lt;code&gt;INSERT INTO messages&lt;/code&gt; against &lt;strong&gt;MySQL&lt;/strong&gt; (converting the UUID via &lt;code&gt;DaoHelper&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MySQL&lt;/strong&gt; returns &lt;code&gt;ok&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The saved &lt;code&gt;Message&lt;/code&gt; travels back up: &lt;strong&gt;JdbcMessageRepository&lt;/strong&gt; → &lt;strong&gt;MessagesService&lt;/strong&gt; → &lt;strong&gt;MessageController&lt;/strong&gt; → &lt;strong&gt;Frontend Client&lt;/strong&gt; as &lt;code&gt;201 { message }&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The sequence diagram
&lt;/h3&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%2Fjikmyhmwdhsjvjjroq8x.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%2Fjikmyhmwdhsjvjjroq8x.png" alt="Figure 5. Sequence diagram for " width="800" height="393"&gt;&lt;/a&gt;Figure 5. Sequence diagram for "Post a message"&lt;/p&gt;

&lt;h3&gt;
  
  
  What this diagram shows that the others couldn't
&lt;/h3&gt;

&lt;p&gt;Look at the &lt;code&gt;alt&lt;/code&gt; block in the middle of the diagram. That's the permission check - the same diamond in the flowchart, the same role-checking responsibility assigned to &lt;code&gt;MessagesService&lt;/code&gt; in the functional diagram. But now it has an exact form: a comparison against the &lt;code&gt;PRODUCER&lt;/code&gt; role, returning &lt;code&gt;403 Forbidden&lt;/code&gt; if it fails, with the response passing back through &lt;code&gt;MessageController&lt;/code&gt; to the client.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;traceability&lt;/strong&gt; working in practice. The validation step in the flowchart → the &lt;code&gt;MessagesService&lt;/code&gt; block in the functional diagram → the &lt;code&gt;HttpResponseExtractor → User → Roles&lt;/code&gt; call chain in the sequence diagram. One idea, three levels of resolution.&lt;/p&gt;

&lt;p&gt;Notice also what the sequence diagram adds that neither previous diagram could: &lt;strong&gt;the reactive chain&lt;/strong&gt;. The &lt;code&gt;UMSConnector&lt;/code&gt;call is non-blocking - &lt;code&gt;MessagesService&lt;/code&gt; issues the &lt;code&gt;WebClient&lt;/code&gt; request and the rest of the chain executes inside a &lt;code&gt;.flatMap()&lt;/code&gt;. The sequence diagram makes this visible through the activation boxes: &lt;code&gt;MS&lt;/code&gt; stays active while &lt;code&gt;UC&lt;/code&gt; and &lt;code&gt;UMS&lt;/code&gt; do their work, then resumes. That concurrency detail is invisible in both the flowchart and the functional diagram.&lt;/p&gt;

&lt;p&gt;With all three diagrams drawn, one use case - "Post a message" - now has a complete, layered blueprint. The next section puts all three side by side and shows what traceability looks like end to end.&lt;/p&gt;

&lt;p&gt;Let's pause before moving on and do something the three diagrams individually don't do: put them side by side and trace a single idea across all of them.&lt;/p&gt;

&lt;p&gt;Take the permission check - the moment the system decides whether the author is allowed to post. Here is where it lives in each diagram:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Diagram&lt;/th&gt;
&lt;th&gt;Where the permission check appears&lt;/th&gt;
&lt;th&gt;What you can see&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flowchart&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The diamond &lt;em&gt;"Is user allowed to post?"&lt;/em&gt; branching to &lt;code&gt;Error&lt;/code&gt;or continuing to validation&lt;/td&gt;
&lt;td&gt;The &lt;em&gt;logic&lt;/em&gt; - two outcomes, one decision&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functional diagram&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The arrow from &lt;code&gt;MessagesService&lt;/code&gt; labelled &lt;em&gt;"check roles contains PRODUCER"&lt;/em&gt; pointing to &lt;code&gt;Roles&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The &lt;em&gt;ownership&lt;/em&gt; - which component holds this responsibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sequence diagram&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;alt&lt;/code&gt; block: &lt;code&gt;HttpResponseExtractor → User → Roles&lt;/code&gt;, branching to &lt;code&gt;403 Forbidden&lt;/code&gt; or &lt;code&gt;INSERT&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The &lt;em&gt;mechanics&lt;/em&gt; - what data moves, in what order, producing what result&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same check. Three levels of resolution. You could point at the diamond in the flowchart, follow it to the &lt;code&gt;MessagesService&lt;/code&gt;arrow in the functional diagram, and land on the &lt;code&gt;alt&lt;/code&gt; block in the sequence diagram - and at each step you'd learn something the previous diagram couldn't tell you.&lt;/p&gt;

&lt;p&gt;That property is &lt;strong&gt;traceability&lt;/strong&gt;: the ability to follow a single requirement from its highest-level expression down to its lowest-level implementation without losing the thread. It's what separates a set of diagrams that decorate a document from a set that actually functions as a blueprint.&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%2Ffk1k8s8gfv65tivh8u7v.jpg" 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%2Ffk1k8s8gfv65tivh8u7v.jpg" alt="Figure 6. One feature, three views - the permission check traced across all three diagrams" width="800" height="402"&gt;&lt;/a&gt;Figure 6. One feature, three views - the permission check traced across all three diagrams&lt;/p&gt;

&lt;p&gt;Notice also what traceability reveals in the other direction. The sequence diagram shows that the &lt;code&gt;403 Forbidden&lt;/code&gt; response travels back through &lt;code&gt;MessageController&lt;/code&gt; before reaching the client - a detail invisible in both the flowchart (which just says "Error → END") and the functional diagram (which shows &lt;code&gt;MessageController&lt;/code&gt; connected to &lt;code&gt;GlobalExceptionHandler&lt;/code&gt; without explaining when). The diagrams don't just repeat each other. Each one completes the picture in a direction the others leave open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations and Open Questions
&lt;/h2&gt;

&lt;p&gt;Three diagrams drawn, one use case traced. Before closing, it's worth being clear about what this process is - and what it isn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagrams go stale.&lt;/strong&gt; Every diagram in this lesson reflects the codebase as it stands today. The moment a new field is added to &lt;code&gt;messages&lt;/code&gt;, or the permission model changes, or &lt;code&gt;UMSConnector&lt;/code&gt; is replaced by a shared auth library - the diagrams need updating. A diagram that isn't maintained becomes misleading faster than no diagram at all. The discipline of drawing diagrams is only half the work; the other half is keeping them honest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is a snapshot, not a specification.&lt;/strong&gt; The diagrams here describe what &lt;code&gt;Bird&lt;/code&gt; does, not what it must do. A specification would go further - stating invariants, error contracts, performance bounds, and edge cases. These diagrams are the foundation of a spec, not the spec itself. They answer "how does it work?" not "how must it always work?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Over-modelling is a real risk.&lt;/strong&gt; Drawing all three diagrams for every feature in a large system would consume more time than it saves. The three-lens approach is most valuable at decision points: when designing a new feature from scratch, when onboarding someone to an unfamiliar part of the system, or when debugging behaviour that contradicts the mental model. Applied indiscriminately, it becomes ceremony rather than engineering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These diagrams are not UML.&lt;/strong&gt; Readers familiar with the Unified Modelling Language (UML) will notice that the diagrams in this lesson borrow UML conventions - sequence diagram lifelines, flowchart decision diamonds - without following the full UML standard. That's deliberate. UML is precise and complete, and its precision comes at the cost of accessibility. However when you move to production systems with formal specification requirements, UML or C4 may be the right choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Three lessons in, and &lt;code&gt;Bird&lt;/code&gt; has a bounded scope, a real data model, and a complete three-lens blueprint for its core feature.&lt;/p&gt;

&lt;p&gt;What it doesn't have yet is a working API. The diagrams show that a &lt;code&gt;POST /messages/message&lt;/code&gt; endpoint should exist, that it must accept &lt;code&gt;{ author, content }&lt;/code&gt;, that it must call UMS before writing to the database - but none of that is running code yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 4&lt;/strong&gt; is where the diagrams meet the wire. We'll define the API contracts and endpoints that connect &lt;code&gt;Bird&lt;/code&gt;'s components - what each service exposes, what it expects, and what it returns. The sequence diagram already told us that &lt;code&gt;POST /messages/message&lt;/code&gt; must exist, that it calls &lt;code&gt;GET /users/user/{id}&lt;/code&gt; on UMS, and that both exchanges carry specific payloads. Lesson 4 makes those contracts explicit and formal: request shapes, response shapes, status codes, and the communication protocol between the Twitter Service and UMS.&lt;/p&gt;

&lt;p&gt;The diagrams were never the destination. They were the clearest possible way to know what to build before building it.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>development</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>The Blueprint Beneath the Blueprint: Designing Data Model and Choosing Its Database</title>
      <dc:creator>Eugene Zimin</dc:creator>
      <pubDate>Sat, 23 May 2026 06:36:22 +0000</pubDate>
      <link>https://dev.to/eugene-zimin/the-blueprint-beneath-the-blueprint-designing-data-model-and-choosing-its-database-3bhl</link>
      <guid>https://dev.to/eugene-zimin/the-blueprint-beneath-the-blueprint-designing-data-model-and-choosing-its-database-3bhl</guid>
      <description>&lt;h1&gt;
  
  
  Lesson 2 of &lt;em&gt;Build a Twitter Clone&lt;/em&gt; - A Practical Guide to Software Modelling
&lt;/h1&gt;

&lt;p&gt;A diagram shows you &lt;em&gt;what&lt;/em&gt; a system does; a data model tells you &lt;em&gt;what it remembers&lt;/em&gt;. Before drawing a single flowchart, you need to know what information &lt;code&gt;Bird&lt;/code&gt; must store - and how that information is shaped. In this lesson we read our three use cases for data clues, name the entities the system must track, define their fields and relationships, and translate all of it into a real MySQL schema split across two purposefully separated databases. &lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction - Data Before Diagrams
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/eugene-zimin/from-idea-to-blueprint-turning-a-vague-app-concept-into-something-you-can-actually-build-1a60"&gt;Lesson 1&lt;/a&gt; was closed with a promise: in Lesson 2, we draw the diagrams. Flowchart, functional diagram, sequence diagram - the works. At the moment we're going to defer it for a while. The reason is quite simple - because of something that becomes obvious the moment you try to draw the flowchart for &lt;em&gt;"Post a message"&lt;/em&gt; without it: &lt;strong&gt;a diagram that doesn't know what data it's moving is vague in exactly the wrong places.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You need to consider the following - the flowchart for posting a message will eventually have a step called something like &lt;em&gt;"save the message."&lt;/em&gt; Straightforward enough on paper. But the moment you try to build it, questions pile up fast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Save &lt;em&gt;what&lt;/em&gt;, exactly? The text? The author? A timestamp? All three?&lt;/li&gt;
&lt;li&gt;Where does the author's identity come from - a name typed into a field, or a reference to a stored account?&lt;/li&gt;
&lt;li&gt;What does "the author" even mean to the system - a row in a table somewhere, or just a string?&lt;/li&gt;
&lt;li&gt;If the same author posts twice, how does the system know both messages belong to the same person?&lt;/li&gt;
&lt;/ul&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%2Fle03a2dorqy1rx3xb0p2.jpg" 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%2Fle03a2dorqy1rx3xb0p2.jpg" alt="Figure 1. What is the Message?" width="800" height="585"&gt;&lt;/a&gt;Figure 1. What is the Message?&lt;/p&gt;

&lt;p&gt;A diagram that leaves those questions open isn't a blueprint. It's a sketch - useful for thinking, but not yet useful for building. The answers live in the &lt;strong&gt;data model&lt;/strong&gt;: the formal description of what the system stores, and how the pieces of stored information relate to one another.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Think of the data model as the system's long-term memory. The diagrams describe what the system &lt;em&gt;does&lt;/em&gt;- its behaviour, its structure, its conversations. The data model describes what it &lt;em&gt;remembers&lt;/em&gt; between those moments of action. Without memory, each request starts from nothing. With it, a message posted today is still there tomorrow, and the author who posted it can be found.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Getting the data model right before drawing the diagrams isn't pedantry. It's the thing that turns vague boxes into precise components. When you know that a &lt;code&gt;message&lt;/code&gt; is a row with an &lt;code&gt;id&lt;/code&gt;, a &lt;code&gt;content&lt;/code&gt; field capped at 280 characters, and an &lt;code&gt;author_id&lt;/code&gt; that points to a specific user - then the "save message" step in your flowchart stops being a placeholder and starts being an instruction. The functional block that does the saving has a clear contract. The sequence diagram can name what passes between components.&lt;/p&gt;

&lt;h2&gt;
  
  
  One System, Two Databases - and Why That's the Right Call
&lt;/h2&gt;

&lt;p&gt;Before we write a single table definition, there's a structural question to answer: should all of &lt;code&gt;Bird&lt;/code&gt;'s data live in one database, or more than one?&lt;/p&gt;

&lt;p&gt;The instinct is usually to start with one. It's simpler, it requires less setup, and for a small project it feels like the obvious default. We're going to make a different call - and the reasoning behind it is worth understanding clearly, because the same question will come up in every non-trivial system you ever build.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem with putting everything in one place
&lt;/h3&gt;

&lt;p&gt;Start with the simpler option: one database called &lt;code&gt;bird&lt;/code&gt;, with all tables inside it - users, messages, sessions, roles, everything. Will it work?&lt;/p&gt;

&lt;p&gt;The answer is clear - it would work. In fact, it's how most beginner projects start, and there's nothing wrong with it as a starting point. But consider what happens as the system grows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A security audit requires changes to how passwords are stored. To make that change safely, you need to understand every table that touches user data - except now it's tangled up with message tables, timeline queries, and subscription records. The scope of "change one thing" has quietly expanded.&lt;/li&gt;
&lt;li&gt;Message volume spikes. You want to move the &lt;code&gt;messages&lt;/code&gt; table to faster storage, or archive old records. But the table is in the same database as your user credentials, which have entirely different performance and retention requirements. You can't move one without the other.&lt;/li&gt;
&lt;li&gt;A colleague is working on authentication while you're working on the timeline feature. Both of you are making schema changes in the same database, to tables that are conceptually unrelated. You're in each other's way.&lt;/li&gt;
&lt;/ul&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%2Fon0zs0hz2huf7j033goq.jpg" 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%2Fon0zs0hz2huf7j033goq.jpg" alt=" " width="800" height="586"&gt;&lt;/a&gt; Figure 2. All in one Database - Will it Work?&lt;/p&gt;

&lt;p&gt;The deeper problem is this: a single database couples concerns that have no real reason to be coupled. They are in the same place not because they belong together, but because it was convenient to put them there. Convenience now, complexity later.&lt;/p&gt;

&lt;h3&gt;
  
  
  The principle: each domain owns its data
&lt;/h3&gt;

&lt;p&gt;The solution is to give each distinct area of the problem its own data store - its own database that it, and only it, is responsible for. Nothing else writes directly to that data; anything that needs information from another domain has to go through the interface that domain exposes.&lt;/p&gt;

&lt;p&gt;This is a well-established pattern in software design called &lt;strong&gt;&lt;a href="https://dev.to/eugene-zimin/database-per-service-as-a-design-pattern-44gi"&gt;Database per Service&lt;/a&gt;&lt;/strong&gt;: each logical service or domain owns its storage, and the boundary between services is enforced at the data layer, not just at the code layer. The pattern makes the separation real and durable - you can't accidentally reach across a boundary you'd have to explicitly cross.&lt;/p&gt;

&lt;p&gt;![[Database per Service Design Pattern.jpg]]Figure 3. Database per Service Design Pattern&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;Bird&lt;/code&gt;, two domains emerge clearly once you ask the question:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identity and access&lt;/strong&gt; - who people are, how they prove it, what permissions they hold, which sessions are currently active. This is security-critical data, relatively stable, and completely self-contained. It has nothing to do with messages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content and social graph&lt;/strong&gt; - the messages people post, and the follow relationships between them. This data is high-volume and fast-moving, with entirely different performance and retention characteristics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two concerns have different owners, different change rates, and different security requirements. They will be given separate databases: &lt;code&gt;ums&lt;/code&gt; (User Management System) for identity and access, and &lt;code&gt;twitter&lt;/code&gt; for content and social graph.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Database&lt;/th&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;What it owns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ums&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Identity &amp;amp; access&lt;/td&gt;
&lt;td&gt;Who people are, how they authenticate, what permissions they hold, which sessions are active&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;twitter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Content &amp;amp; social graph&lt;/td&gt;
&lt;td&gt;The messages people post, and who follows whom&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Separating them means each can be optimized, scaled, or secured independently - a schema change in one won't touch the other.&lt;/p&gt;

&lt;h3&gt;
  
  
  The idea behind the split: bounded contexts
&lt;/h3&gt;

&lt;p&gt;The deeper principle at work here comes from &lt;strong&gt;Domain-Driven Design&lt;/strong&gt; (DDD) - a way of thinking about how to organize software around the real-world problems it solves, rather than around technical convenience.&lt;/p&gt;

&lt;p&gt;DDD would describe &lt;code&gt;ums&lt;/code&gt; and &lt;code&gt;twitter&lt;/code&gt; as separate &lt;strong&gt;bounded contexts&lt;/strong&gt;: distinct areas of the problem, each with its own vocabulary, rules, and data. The word &lt;em&gt;user&lt;/em&gt; in the identity context means something specific - an account with credentials, roles, and a session history. The word &lt;em&gt;author&lt;/em&gt; in the messaging context means something different - a source of content, identified by an ID, whose full identity details live elsewhere. These concepts correspond to the same human being, but they are different models of that person, serving different purposes. Keeping them in separate databases makes that distinction visible and enforces it.&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%2F0zuxzd8pblp65lipqulm.jpg" 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%2F0zuxzd8pblp65lipqulm.jpg" alt=" " width="800" height="586"&gt;&lt;/a&gt; Figure 4. How DDD Helps and Works&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;An analogy.&lt;/strong&gt; Think of a hospital. The billing department and the medical records department both deal with the same patients - but they maintain completely separate files. The billing system doesn't need to know a patient's diagnosis; the medical records system doesn't need to know their payment history. Each department owns its data. Information is shared only when explicitly requested, through defined channels. The separation isn't bureaucracy - it's what keeps sensitive data appropriately contained, and what lets each department evolve independently.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want to go deeper on Domain-Driven Design, &lt;a href="https://dev.to/eugene-zimin/leveraging-domain-driven-design-for-application-design-58e2"&gt;this article&lt;/a&gt; covers the core ideas without requiring a computer science background.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the Use Cases for Data Clues
&lt;/h2&gt;

&lt;p&gt;A use case describes what a user accomplishes. But read carefully, and it also tells you what the system must &lt;em&gt;remember&lt;/em&gt; in order to make that possible. Each of &lt;code&gt;Bird&lt;/code&gt;'s three use cases leaves a trail of data requirements - we just have to follow it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Post a message
&lt;/h3&gt;

&lt;p&gt;A user writes some text and publishes it. For this to work, the system must store the text itself, know &lt;em&gt;who&lt;/em&gt; posted it, and record &lt;em&gt;when&lt;/em&gt; it was posted so the timeline can be ordered. That's three pieces of information: content, author, timestamp.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Why it's needed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;messages&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UUID&lt;/td&gt;
&lt;td&gt;A stable, unique identifier for this message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;messages&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;author_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UUID&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;id&lt;/code&gt; of the user who posted this message - links back to &lt;code&gt;users.id&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;messages&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;content&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;String (max 280 chars)&lt;/td&gt;
&lt;td&gt;The text of the message, capped at 280 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;messages&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Timestamp&lt;/td&gt;
&lt;td&gt;When the message was posted - used to order the timeline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  View the timeline
&lt;/h3&gt;

&lt;p&gt;This use case produces no new fields. It reads existing &lt;code&gt;messages&lt;/code&gt; rows, ordered by &lt;code&gt;created_at&lt;/code&gt; descending, and joins to &lt;code&gt;users&lt;/code&gt; on &lt;code&gt;author_id&lt;/code&gt; to display the author's name. Every field it depends on was already required by &lt;em&gt;Post a message&lt;/em&gt;. Here we have to introduce what it is called - &lt;em&gt;subscriptions&lt;/em&gt; and set a relation between author and and consumer of the message, i.e. - subscriber.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Why it's needed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;subscriptions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;subscriber_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UUID&lt;/td&gt;
&lt;td&gt;The user doing the following - logical FK to &lt;code&gt;users.id&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;subscriptions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;producer_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UUID&lt;/td&gt;
&lt;td&gt;The user being followed - logical FK to &lt;code&gt;users.id&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;subscriptions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Timestamp&lt;/td&gt;
&lt;td&gt;When the follow relationship was created&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Register an account
&lt;/h3&gt;

&lt;p&gt;A user creates an identity. The system must store a name to display, credentials to authenticate with (stored as a hashed password, never plain text), and again a timestamp. It also needs a way to distinguish one account from every other - a unique identifier that never changes even if the username does.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Why it's needed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;users&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UUID&lt;/td&gt;
&lt;td&gt;A stable, unique identifier for this user - never changes, even if name or email does&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;users&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;The display name shown alongside every post&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;users&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;Login credential; unique across all users - no two accounts can share an address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;users&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;password_hash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;The user's password after a one-way hashing function - never the plain-text password&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;users&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Timestamp&lt;/td&gt;
&lt;td&gt;When the account was registered&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;users&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;updated_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Timestamp&lt;/td&gt;
&lt;td&gt;When any field on this record last changed; refreshed automatically on every write&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Why it's needed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;roles&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UUID&lt;/td&gt;
&lt;td&gt;Unique identifier for this role&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;roles&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;The role label (e.g. &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;member&lt;/code&gt;) - must be unique&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;roles&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;Optional human-readable explanation of what this role permits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Why it's needed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sessions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UUID&lt;/td&gt;
&lt;td&gt;Unique identifier for this login session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sessions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;user_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UUID&lt;/td&gt;
&lt;td&gt;References &lt;code&gt;users.id&lt;/code&gt; - whose session this is&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sessions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;logged_in_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Timestamp&lt;/td&gt;
&lt;td&gt;When the session started&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sessions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;logged_out_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Timestamp&lt;/td&gt;
&lt;td&gt;When the session ended; &lt;code&gt;NULL&lt;/code&gt; if the user is still logged in&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Across all three use cases, two distinct categories of information emerge: things the system needs to know about &lt;em&gt;people&lt;/em&gt;, and things it needs to know about &lt;em&gt;messages&lt;/em&gt;. That observation is the foundation of the data model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naming the Entities
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;entity&lt;/strong&gt; is a category of information the system tracks as a distinct thing - something that has its own identity, its own set of properties, and its own lifetime. Naming entities is the first act of data modelling: you're deciding what the system considers a &lt;em&gt;noun&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;From the use cases, two entities name themselves:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;User&lt;/code&gt;&lt;/strong&gt; - a registered account. Every message is posted by a user; the timeline attributes each post to one. Users exist independently of any message they've posted, and they persist even if all their messages were deleted. They are a distinct thing in their own right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Message&lt;/code&gt;&lt;/strong&gt; - a piece of content posted by a user. Messages depend on users (a message with no author makes no sense), but they are not &lt;em&gt;part of&lt;/em&gt; a user - they are their own thing, with their own timestamp and their own text.&lt;/p&gt;

&lt;p&gt;Two entities. That matches the two databases we decided to create: &lt;code&gt;ums&lt;/code&gt; owns &lt;code&gt;User&lt;/code&gt;, and &lt;code&gt;twitter&lt;/code&gt; owns &lt;code&gt;Message&lt;/code&gt;. The database boundary and the entity boundary are the same line drawn twice.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You'll notice there's no &lt;code&gt;Timeline&lt;/code&gt; entity, no &lt;code&gt;Feed&lt;/code&gt; entity, no &lt;code&gt;Notification&lt;/code&gt;. The timeline is not a thing the system stores - it's a &lt;em&gt;query&lt;/em&gt; - give me all messages, ordered by &lt;code&gt;created_at&lt;/code&gt;, descending. It exists at runtime, not at rest. This is a useful distinction to internalise: not everything the user &lt;em&gt;sees&lt;/em&gt; needs to be &lt;em&gt;stored&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the next section we'll define the fields each entity carries - and introduce the mechanism that connects a &lt;code&gt;Message&lt;/code&gt;back to the &lt;code&gt;User&lt;/code&gt; who wrote it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining the Fields
&lt;/h2&gt;

&lt;p&gt;An entity is just a name until you give it fields - the individual pieces of data it holds. This is where the model gets concrete. For each field, we'll state what it is, what type of value it holds, and &lt;em&gt;why it exists&lt;/em&gt;, tracing every decision back to a use case or a design constraint.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note on identifiers
&lt;/h3&gt;

&lt;p&gt;Every entity needs a &lt;strong&gt;primary key&lt;/strong&gt; - a field whose sole job is to uniquely identify one row among all others, forever. A common choice is an auto-incrementing integer (1, 2, 3...), but &lt;code&gt;Bird&lt;/code&gt; uses something different: a &lt;strong&gt;UUID&lt;/strong&gt; (Universally Unique Identifier), stored as 16 bytes (or 128 bits) of binary data.&lt;/p&gt;

&lt;p&gt;A UUID looks like &lt;code&gt;550e8400-e29b-41d4-a716-446655440000&lt;/code&gt; - a 128-bit value generated in a way that makes collisions statistically impossible, even across separate systems. The binary storage (&lt;code&gt;BINARY(16)&lt;/code&gt;) keeps it compact and fast to index. The reason to prefer UUIDs over integers here is forward-looking: if &lt;code&gt;Bird&lt;/code&gt; ever scales to multiple servers generating records simultaneously, each can produce its own IDs without coordinating with the others. Integers can't do that safely.&lt;/p&gt;

&lt;p&gt;Every table uses this same pattern for its primary key: a &lt;code&gt;BINARY(16)&lt;/code&gt; column called &lt;code&gt;id&lt;/code&gt;, defaulting to a freshly generated UUID.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;users&lt;/code&gt; - the identity record
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;users&lt;/code&gt; table lives in the &lt;code&gt;ums&lt;/code&gt; database. It is the authoritative record of everyone who has registered an account.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BINARY(16)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Primary key - uniquely identifies this user across the entire system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VARCHAR(100)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The display name shown on posts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VARCHAR(255)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Login credential and contact address; must be unique across all users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;password_hash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VARCHAR(255)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The result of running the user's password through a one-way hashing function - never the password itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DATETIME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When the account was registered&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;updated_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DATETIME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When any field on this record was last changed; updated automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two fields deserve a word of explanation. &lt;code&gt;password_hash&lt;/code&gt; stores a hashed password, not a plain-text one. A &lt;strong&gt;hash function&lt;/strong&gt;is a one-way transformation: you can turn a password into a hash, but you cannot reverse the process. When a user logs in, the system hashes what they typed and compares the result to the stored hash - the original password never needs to be stored or retrieved. This is standard practice; storing plain passwords is a serious security failure.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;updated_at&lt;/code&gt; tracks the last modification time and refreshes itself automatically on every write. It's low-cost to store and invaluable for debugging, auditing, and cache invalidation later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supporting the &lt;code&gt;User&lt;/code&gt;: roles and sessions
&lt;/h3&gt;

&lt;p&gt;The use cases named registration - but a real identity system has two more concerns lurking just beneath the surface: &lt;em&gt;what is this user allowed to do&lt;/em&gt;, and &lt;em&gt;is this user currently logged in&lt;/em&gt;? These concerns get their own tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;roles&lt;/code&gt;&lt;/strong&gt; is a lookup table - a simple list of named permission levels (for example, &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;moderator&lt;/code&gt;, &lt;code&gt;member&lt;/code&gt;). Roles don't come from the use cases directly; they come from the reality that not all users have the same permissions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BINARY(16)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Primary key — uniquely identifies this role&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VARCHAR(50)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The role label (e.g. &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;member&lt;/code&gt;) — must be unique&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VARCHAR(255)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optional human-readable explanation of what this role permits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;users_roles&lt;/code&gt;&lt;/strong&gt; links users to roles. Because one user can hold multiple roles and one role can be held by many users, this is a &lt;strong&gt;many-to-many relationship&lt;/strong&gt; - and the standard way to model that in a relational database is a join table: a table with two columns, each a reference to one side of the relationship.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;sessions&lt;/code&gt;&lt;/strong&gt; records active login sessions. When a user authenticates, a session row is created with a &lt;code&gt;logged_in_at&lt;/code&gt; timestamp. When they log out, &lt;code&gt;logged_out_at&lt;/code&gt; is filled in. This gives the system a full audit trail of who was logged in and when - and lets it invalidate specific sessions without forcing a global logout.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BINARY(16)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Primary key — uniquely identifies this session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BINARY(16)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;References &lt;code&gt;ums.users.id&lt;/code&gt; — whose session this is&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;logged_in_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DATETIME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When the session started&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;logged_out_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DATETIME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When the session ended; &lt;code&gt;NULL&lt;/code&gt; if the user is still logged in&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these tables store messages or content. They belong entirely to the &lt;code&gt;ums&lt;/code&gt; database and the identity domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;messages&lt;/code&gt; - the content record
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;messages&lt;/code&gt; table lives in the &lt;code&gt;twitter&lt;/code&gt; database. It is the record of everything posted.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BINARY(16)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Primary key - uniquely identifies this message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;author_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BINARY(16)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;id&lt;/code&gt; of the user who posted this message, from &lt;code&gt;ums.users&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;content&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VARCHAR(280)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The text of the message - capped at 280 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DATETIME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When the message was posted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;content&lt;/code&gt; is capped at 280 characters - the same limit Twitter uses, and a deliberate product constraint, not a technical one. The database enforces it with &lt;code&gt;VARCHAR(280)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;author_id&lt;/code&gt; is the field that links a message to its author. It holds the &lt;code&gt;id&lt;/code&gt; value of a row in &lt;code&gt;ums.users&lt;/code&gt;. Because the two tables live in separate databases, MySQL cannot enforce this link with a formal foreign key constraint - but the relationship is real. The application layer is responsible for ensuring that no message is ever written with an &lt;code&gt;author_id&lt;/code&gt; that doesn't correspond to a real user.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;subscriptions&lt;/code&gt; - the social graph
&lt;/h3&gt;

&lt;p&gt;There is one more table in the &lt;code&gt;twitter&lt;/code&gt; database: &lt;code&gt;subscriptions&lt;/code&gt;. It didn't appear in the original three use cases, but it's present in the schema for a good reason - it's the data that would power a personalised timeline.&lt;/p&gt;

&lt;p&gt;A subscription is a directional relationship between two users: one &lt;strong&gt;subscriber&lt;/strong&gt; who follows one &lt;strong&gt;producer&lt;/strong&gt;. The table has just three fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;subscriber_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BINARY(16)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The user doing the following&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;producer_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BINARY(16)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The user being followed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DATETIME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When the follow happened&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The combination of &lt;code&gt;subscriber_id&lt;/code&gt; and &lt;code&gt;producer_id&lt;/code&gt; is the primary key - you can only follow someone once. Both columns are logical foreign keys to &lt;code&gt;ums.users.id&lt;/code&gt;, subject to the same cross-database constraint limitation as &lt;code&gt;author_id&lt;/code&gt; in &lt;code&gt;messages&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;subscriptions&lt;/code&gt; is infrastructure for a feature - &lt;em&gt;"Follow a user"&lt;/em&gt; - that isn't in scope for the current lesson's use cases but is correct to model now, because adding it later would require a migration. Modelling it upfront costs nothing; omitting it and adding it later costs a schema change and a deployment. This is the kind of forward-thinking that separates a considered data model from a reactive one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Entities Relate
&lt;/h2&gt;

&lt;p&gt;Individual entities are only half the picture. A data model also defines how entities connect to one another - their &lt;strong&gt;relationships&lt;/strong&gt;. For &lt;code&gt;Bird&lt;/code&gt;, there are two:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A User has many Messages&lt;/strong&gt; (one-to-many). One user can post any number of messages; each message belongs to exactly one user. This relationship is expressed through &lt;code&gt;author_id&lt;/code&gt; in the &lt;code&gt;messages&lt;/code&gt; table - a field that holds the &lt;code&gt;id&lt;/code&gt; of the user who owns that row. Following the &lt;code&gt;author_id&lt;/code&gt; from a message leads you to its author.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A User can follow many Users, and be followed by many Users&lt;/strong&gt; (many-to-many). This is the social graph, modelled through the &lt;code&gt;subscriptions&lt;/code&gt; table. To find everyone a user follows, query &lt;code&gt;subscriptions&lt;/code&gt; where &lt;code&gt;subscriber_id&lt;/code&gt; matches. To find everyone following a user, query where &lt;code&gt;producer_id&lt;/code&gt; matches.&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%2Flpbs4aqnxlbfitwczp17.jpg" 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%2Flpbs4aqnxlbfitwczp17.jpg" alt=" " width="800" height="539"&gt;&lt;/a&gt;Figure 5. Types of Relationships&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The pattern to remember.&lt;/strong&gt; A one-to-many relationship is expressed by putting the "one" side's &lt;code&gt;id&lt;/code&gt; as a field on the "many" side - the foreign key lives in the child table. A many-to-many relationship requires its own table, with one column for each side. Both patterns appear in &lt;code&gt;Bird&lt;/code&gt;, and together they cover the vast majority of real-world data relationships you'll encounter.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With the entities named, the fields defined, and the relationships mapped, the data model is complete. What remains is to choose a database engine and write the schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Diagrams Will Now Be Built On
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/eugene-zimin/from-idea-to-blueprint-turning-a-vague-app-concept-into-something-you-can-actually-build-1a60"&gt;Lesson 1&lt;/a&gt; introduced three lenses for looking at a system: functional structure, behaviour, and component interaction. &lt;a href="https://dev.to/eugene-zimin/from-idea-to-blueprint-turning-a-vague-app-concept-into-something-you-can-actually-build-1a60"&gt;Lesson 1&lt;/a&gt; also made a promise — that diagrams would follow.&lt;/p&gt;

&lt;p&gt;They will. But notice what's changed since then.&lt;/p&gt;

&lt;p&gt;Before the data model existed, a flowchart for &lt;em&gt;"Post a message"&lt;/em&gt; would have had a step labelled something like &lt;em&gt;"save the message"&lt;/em&gt; — a placeholder that gestures at an action without defining it. Now that step has a precise meaning: create a row in &lt;code&gt;twitter.messages&lt;/code&gt; with a &lt;code&gt;content&lt;/code&gt; value, an &lt;code&gt;author_id&lt;/code&gt; pointing to the authenticated user in &lt;code&gt;ums.users&lt;/code&gt;, and a &lt;code&gt;created_at&lt;/code&gt;timestamp set to now.&lt;/p&gt;

&lt;p&gt;The data model doesn't just inform the diagrams — it creates clear mechanics how they work. Every box that reads or writes data now has a contract: specific fields, specific tables, specific relationships. The sequence diagram will name what passes between components. The functional diagram will know what each block owns. The flowchart steps will correspond to real operations.&lt;/p&gt;

&lt;p&gt;That's where &lt;a href="https://dev.to/eugene-zimin/drawing-the-blueprint-flowchart-functional-diagram-and-sequence-diagram-537c"&gt;Lesson 3&lt;/a&gt; picks up: with the data model as the foundation, we draw all three diagrams.&lt;/p&gt;

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

&lt;p&gt;A data model is the contract the rest of the system is written against.&lt;/p&gt;

&lt;p&gt;We started with three use cases and asked a simple question: what must the system remember for each of these to work? That question led us to two entities — &lt;code&gt;User&lt;/code&gt; and &lt;code&gt;Message&lt;/code&gt; — and from there to five tables across two purposefully separated databases.&lt;/p&gt;

&lt;p&gt;The separation itself was a design decision, not a convenience. Identity and access belong to one domain; content and social graph belong to another. Keeping them apart makes each easier to change, scale, and reason about independently. The trade-off — enforcing cross-database relationships in application code rather than at the database level — is a known and manageable cost.&lt;/p&gt;

&lt;p&gt;Every field in the schema exists for a reason traceable back to a use case. Every relationship reflects a real dependency between things the system tracks. Nothing was added for completeness or anticipation — except &lt;code&gt;subscriptions&lt;/code&gt;, which earns its place by being cheaper to model now than to migrate in later.&lt;/p&gt;

&lt;p&gt;The diagrams come next. They'll have something real to say.&lt;/p&gt;

</description>
      <category>database</category>
      <category>web</category>
      <category>architecture</category>
      <category>data</category>
    </item>
    <item>
      <title>From Idea to Blueprint: Turning a Vague App Concept into Something You Can Actually Build</title>
      <dc:creator>Eugene Zimin</dc:creator>
      <pubDate>Sun, 17 May 2026 22:03:47 +0000</pubDate>
      <link>https://dev.to/eugene-zimin/from-idea-to-blueprint-turning-a-vague-app-concept-into-something-you-can-actually-build-1a60</link>
      <guid>https://dev.to/eugene-zimin/from-idea-to-blueprint-turning-a-vague-app-concept-into-something-you-can-actually-build-1a60</guid>
      <description>&lt;h1&gt;
  
  
  Lesson 1 of &lt;em&gt;Build a Twitter Clone&lt;/em&gt; — A Practical Guide to Software Modelling
&lt;/h1&gt;

&lt;p&gt;"Build a Twitter clone" is a wish, not a plan — you can't write code from a single sentence. This lesson shows you how to think about turning a fuzzy app idea into a concrete blueprint: we scope a deliberately small messaging app called &lt;code&gt;Bird&lt;/code&gt;, then introduce the three-lens model — &lt;em&gt;what should it do?&lt;/em&gt;, &lt;em&gt;what parts must exist?&lt;/em&gt;, and &lt;em&gt;how do those parts talk to each other?&lt;/em&gt; — that the rest of the series uses to actually draw it. By the end you'll have a bounded project and a clear mental model, the foundation &lt;a href="https://dev.to/eugene-zimin/the-blueprint-beneath-the-blueprint-designing-data-model-and-choosing-its-database-3bhl"&gt;Lesson 2&lt;/a&gt; builds on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction — From Idea to Blueprint
&lt;/h2&gt;

&lt;p&gt;Imagine someone hands you a sticky note that says &lt;strong&gt;"build a Twitter clone"&lt;/strong&gt; and asks you to start coding.&lt;/p&gt;

&lt;p&gt;Where, exactly, would you put your cursor?&lt;/p&gt;

&lt;p&gt;That question is harder than it looks, and the difficulty isn't about programming skill. It's that the instruction isn't actually buildable. "Build a Twitter clone" is a &lt;em&gt;wish&lt;/em&gt;. It tells you the destination but none of the road. Before a single line of code makes sense, that wish has to be transformed into something with edges, parts, and order — a &lt;strong&gt;blueprint&lt;/strong&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%2Fn6g4ur1za11qelsqrz75.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%2Fn6g4ur1za11qelsqrz75.png" alt="Figure 1. From Idea to Blueprint" width="800" height="597"&gt;&lt;/a&gt;Figure 1. From Idea to Blueprint&lt;/p&gt;

&lt;p&gt;This lesson is about making that transformation, and doing it with a tool most people underrate: the humble diagram.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why an idea isn't a plan
&lt;/h3&gt;

&lt;p&gt;A sentence like "build a Twitter clone" hides an enormous number of decisions. Consider just a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can users edit a post after publishing it, or is it permanent?&lt;/li&gt;
&lt;li&gt;What happens when someone submits an empty message? An error? Silence?&lt;/li&gt;
&lt;li&gt;Does a post appear instantly, or is there a moment of "sending..."?&lt;/li&gt;
&lt;li&gt;Where does the message &lt;em&gt;go&lt;/em&gt; once it's submitted — and who is responsible for keeping it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these answers live in the original sentence. They have to be &lt;em&gt;decided&lt;/em&gt;, and a working app is really just the sum of hundreds of such decisions made consistent with one another. The gap between "an idea" and "a plan" is precisely the gap of unmade decisions — and code is a terrible place to discover you forgot one.&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%2Fl2ug5mvfc1qn39lbdosf.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%2Fl2ug5mvfc1qn39lbdosf.png" alt="Figure 2. Why Idea is not Enough" width="799" height="381"&gt;&lt;/a&gt; Figure 2. Why Idea is not Enough&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A blueprint isn't bureaucracy. It's the cheapest place to be wrong. Moving a wall on paper costs a pencil eraser; moving it after the concrete is poured costs a demolition crew.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The same logic applies to software. A confused diagram can be fixed in thirty seconds. The same confusion, discovered three weeks into coding, can cost days of rework. Thinking before building isn't slower — it's the thing that makes building fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagrams: thinking you can point at
&lt;/h3&gt;

&lt;p&gt;When we say &lt;strong&gt;diagram&lt;/strong&gt;, we don't mean decorative boxes-and-arrows that get drawn after the work is done and quietly go stale. We mean something more useful: a diagram is &lt;em&gt;a way of thinking that you can point at&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Plain prose is bad at certain kinds of thought. A paragraph describing how six components pass data around is exhausting to read and easy to get wrong, because language forces ideas into a single line, one word after another. But a system isn't a line — it has shape. It branches. It loops. It has parts that exist &lt;em&gt;alongside&lt;/em&gt; each other. A diagram lets that shape sit still on the page so you can inspect it, find the gap, and point a teammate straight at the problem.&lt;/p&gt;

&lt;p&gt;That's the real job of the diagrams in this series. They are not documentation. They are &lt;strong&gt;the instrument you use to turn the wish into the plan&lt;/strong&gt; — and, just as importantly, a shared picture a whole team can argue over and agree on.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this series is — and what this lesson does
&lt;/h3&gt;

&lt;p&gt;This is the first lesson in a hands-on series with one practical goal: &lt;strong&gt;build a working Twitter-style application, from blank page to running software.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We won't begin with code, a database, or a framework. We'll begin where every real project begins — with the awkward question &lt;em&gt;"what should this thing even do?"&lt;/em&gt; — and we'll answer it the way experienced engineers do: by modelling the system before constructing it.&lt;/p&gt;

&lt;p&gt;Across this lesson you'll meet &lt;strong&gt;three kinds of diagrams&lt;/strong&gt;, and the order matters, because each one answers a question the next one depends on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Behaviour&lt;/strong&gt; — &lt;em&gt;What should the app do?&lt;/em&gt; We'll capture this with a &lt;strong&gt;flowchart&lt;/strong&gt;: the step-by-step flow of a single user action, including its decision points and outcomes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure&lt;/strong&gt; — &lt;em&gt;What parts must exist to make that behaviour possible?&lt;/em&gt; We'll capture this with a &lt;strong&gt;functional diagram&lt;/strong&gt; (also called a block diagram): the system broken into its major components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interaction&lt;/strong&gt; — &lt;em&gt;How do those parts actually talk to each other at runtime?&lt;/em&gt; We'll capture this with a &lt;strong&gt;sequence diagram&lt;/strong&gt;: the precise back-and-forth of messages between components.&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%2F9f7bcjzivvf05sfvrsv0.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%2F9f7bcjzivvf05sfvrsv0.png" alt="Figure 3. The Cube" width="800" height="800"&gt;&lt;/a&gt; Figure 3. The Cube&lt;/p&gt;

&lt;p&gt;Behaviour first, then the structure that delivers it, then the detailed conversation inside that structure. Each diagram is derived from the one before — you'll watch structure literally &lt;em&gt;fall out of&lt;/em&gt; behaviour rather than being invented from thin air.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't worry if those three terms mean nothing to you yet.&lt;/strong&gt; Each gets a plain-language introduction, a worked example, and a step-by-step recipe in its own section. Right now, the only thing to hold onto is the progression: &lt;strong&gt;what it does → what it's made of → how the parts interact.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  A deliberately small project
&lt;/h3&gt;

&lt;p&gt;There's one honest catch we should name immediately. The real Twitter is gigantic — feeds, ranking algorithms, notifications, direct messages, media uploads, advertising, moderation at planetary scale. We are &lt;em&gt;not&lt;/em&gt; building that. Trying to would teach you nothing except how to feel overwhelmed.&lt;/p&gt;

&lt;p&gt;Instead, we'll build the &lt;strong&gt;smallest thing that is still recognisably a short-messaging app&lt;/strong&gt; — a deliberately tiny slice we'll call &lt;strong&gt;&lt;code&gt;Bird&lt;/code&gt;&lt;/strong&gt;. Choosing that slice on purpose is not a shortcut around the engineering. It &lt;em&gt;is&lt;/em&gt; the engineering. Knowing what to leave out — scoping a &lt;em&gt;minimum viable product&lt;/em&gt;, or &lt;strong&gt;MVP&lt;/strong&gt;, the leanest version that still does the core job — is one of the most valuable skills in software, and it's the first thing the next section will practise.&lt;/p&gt;

&lt;p&gt;So let's pick up the sticky note that says &lt;em&gt;"build a Twitter clone"&lt;/em&gt; — and turn it into something you can actually build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the Project: &lt;code&gt;Bird&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Before we can draw a single diagram, we need something to draw &lt;em&gt;about&lt;/em&gt;. So let's give the vague sticky note a concrete shape.&lt;/p&gt;

&lt;p&gt;We're building &lt;strong&gt;&lt;code&gt;Bird&lt;/code&gt;&lt;/strong&gt;: a small short-messaging app where people post brief public updates and read other people's. That's the whole pitch. If Twitter is a city, &lt;code&gt;Bird&lt;/code&gt; is a single well-built room in it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The hardest decision is what to leave out
&lt;/h3&gt;

&lt;p&gt;It's tempting to start listing everything &lt;code&gt;Bird&lt;/code&gt; &lt;em&gt;could&lt;/em&gt; do — likes, replies, hashtags, profile photos, search, notifications, a ranked feed. Resist that. A feature list that grows without limit is the single most common reason beginner projects never ship.&lt;/p&gt;

&lt;p&gt;The discipline here has a name: scoping a &lt;strong&gt;minimum viable product&lt;/strong&gt;, or &lt;strong&gt;MVP&lt;/strong&gt; — the &lt;em&gt;leanest&lt;/em&gt; version of an app that still does its core job and nothing more. The goal isn't a crippled product. It's a &lt;em&gt;complete&lt;/em&gt; one with a deliberately narrow definition of "complete."&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A useful test for the MVP line:&lt;/strong&gt; for each candidate feature, ask &lt;em&gt;"If this were missing, would the app still be recognisably a short-messaging app?"&lt;/em&gt; If the answer is yes, the feature is &lt;strong&gt;out&lt;/strong&gt; of Lesson 1 — not deleted forever, just postponed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Run that test and most of the obvious features fall away. Likes? The app still works without them. Hashtags? Still works. A ranked feed? Still works — you'd just see posts newest-first. But remove the ability to &lt;em&gt;write&lt;/em&gt; a post, or to &lt;em&gt;see_posts&lt;/em&gt;, and there's no app left at all. Those survive the test. Almost nothing else does.&lt;/p&gt;

&lt;p&gt;This gives us our scope. We'll describe it as a short list of &lt;strong&gt;use cases&lt;/strong&gt; — a use case is simply &lt;em&gt;one complete thing a user can accomplish with the app&lt;/em&gt;, named from the user's point of view. Use cases are the raw material everything else in this lesson is derived from, so naming them precisely matters.&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%2Fz85n36c5cln5cohxf7x5.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%2Fz85n36c5cln5cohxf7x5.png" alt="Figure 4. Defining the MVP - Minimal Viable Product" width="799" height="381"&gt;&lt;/a&gt; Figure 4. Defining the MVP - Minimal Viable Product&lt;/p&gt;

&lt;p&gt;For now, &lt;code&gt;Bird&lt;/code&gt; has exactly three use cases:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;What the user accomplishes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Post a message&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Write a short message (a "bird") and publish it so others can see it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;View the timeline&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;See a list of recently posted messages, newest first.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Register an account&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Create an identity so posts can be attributed to a person.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three use cases. That's the entire behavioural scope of the app — and it's &lt;em&gt;meant&lt;/em&gt; to feel small.&lt;/p&gt;

&lt;h3&gt;
  
  
  One use case becomes our worked example
&lt;/h3&gt;

&lt;p&gt;Three use cases is a small scope, but it's still more than we need to &lt;em&gt;teach&lt;/em&gt; diagramming. Drawing all three through all three diagram types would be repetitive — you'd learn the technique once and then watch it twice more.&lt;/p&gt;

&lt;p&gt;So for the rest of this lesson, we'll follow &lt;strong&gt;a single use case&lt;/strong&gt; end to end: &lt;strong&gt;"Post a message."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We chose it deliberately. Of the three, it's the one with real &lt;em&gt;internal richness&lt;/em&gt; — it involves the user typing, the app checking the input, something deciding whether to accept or reject it, and the message being stored somewhere. That makes it the ideal teaching subject:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it has a clear &lt;strong&gt;start and end&lt;/strong&gt;, so it suits a flowchart;&lt;/li&gt;
&lt;li&gt;it requires &lt;strong&gt;several distinct components&lt;/strong&gt;, so it suits a functional diagram;&lt;/li&gt;
&lt;li&gt;it involves a genuine &lt;strong&gt;back-and-forth between those components&lt;/strong&gt;, so it suits a sequence diagram.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"View the timeline" and "Register an account" are real and necessary, but we'll leave them as exercises. Once you've watched "Post a message" travel through all three diagram types, you'll have the pattern — and applying it to the other two becomes straightforward practice rather than new material.&lt;/p&gt;

&lt;p&gt;Eventually, we've turned a vague wish ("build a Twitter clone") into a &lt;em&gt;bounded&lt;/em&gt; one: an app called &lt;code&gt;Bird&lt;/code&gt;, three named use cases, an explicit out-of-scope list, and one chosen worked example. That bounded definition is the first real artifact of the series. The next section introduces the three lenses we'll view it through — and explains why their order is not arbitrary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Lenses, One Progression
&lt;/h2&gt;

&lt;p&gt;We now have a bounded project: the &lt;code&gt;Bird&lt;/code&gt; app, three use cases, and one worked example — &lt;em&gt;"Post a message."&lt;/em&gt; The next move is to look at that example through three different &lt;strong&gt;lenses&lt;/strong&gt;, each a kind of diagram.&lt;/p&gt;

&lt;p&gt;The word &lt;em&gt;lens&lt;/em&gt; is the right one. A lens doesn't change the thing you're looking at — it changes what you can &lt;em&gt;see&lt;/em&gt; clearly. Point a microscope and a telescope at the same night sky and you learn completely different truths. Our three diagrams work the same way: one system, three views, three distinct questions answered.&lt;/p&gt;

&lt;h3&gt;
  
  
  The three questions
&lt;/h3&gt;

&lt;p&gt;Every software system can be interrogated from three angles. Each diagram type is just a tool purpose-built to answer one of them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lens&lt;/th&gt;
&lt;th&gt;The question it answers&lt;/th&gt;
&lt;th&gt;The diagram&lt;/th&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Behaviour&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What should the app &lt;em&gt;do&lt;/em&gt;?&lt;/td&gt;
&lt;td&gt;Flowchart&lt;/td&gt;
&lt;td&gt;Steps, decisions, and outcomes — the system as a _process_unfolding in time.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Structure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What &lt;em&gt;parts&lt;/em&gt; must exist?&lt;/td&gt;
&lt;td&gt;Functional diagram&lt;/td&gt;
&lt;td&gt;Components and their connections — the system as a &lt;em&gt;thing made of pieces&lt;/em&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Interaction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How do the parts &lt;em&gt;talk&lt;/em&gt;?&lt;/td&gt;
&lt;td&gt;Sequence diagram&lt;/td&gt;
&lt;td&gt;Messages exchanged between components — the system as a &lt;em&gt;conversation&lt;/em&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice that none of these is "more correct" than the others. A flowchart can't tell you what components to build; a functional diagram can't tell you what happens when input is invalid. They are not competing descriptions — they are &lt;strong&gt;complementary&lt;/strong&gt; ones. You need all three because no single one is complete.&lt;/p&gt;

&lt;p&gt;![[Behaviour, Structure, Interaction - 3 Diagram Types for Multi-Dimensional Software Modelling.png]]&lt;br&gt;
Figure 4. Behaviour, Structure, Interaction - 3 Diagram Types for Multi-Dimensional Software Modelling&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;An analogy.&lt;/strong&gt; Think of how you'd describe a car. &lt;em&gt;Behaviour:&lt;/em&gt; "you press the pedal, fuel ignites, the wheels turn." &lt;em&gt;Structure:&lt;/em&gt; "it has an engine, a transmission, four wheels, a fuel tank." &lt;em&gt;Interaction:&lt;/em&gt; "the engine sends torque to the transmission, which sends it to the axle." Each description is true. Each is useless on its own. Together, they let you actually understand — or build — the car.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Why the order is not arbitrary
&lt;/h3&gt;

&lt;p&gt;Here is the part that matters most, and the habit this whole series is built on: &lt;strong&gt;the three lenses form a progression, and the progression has a direction.&lt;/strong&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%2Fn1m7qzqpxlisdkpqg13l.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%2Fn1m7qzqpxlisdkpqg13l.png" alt="Figure 5. The order is important" width="800" height="133"&gt;&lt;/a&gt;Figure 5. The order is important&lt;/p&gt;

&lt;p&gt;You move &lt;strong&gt;behaviour → structure → interaction&lt;/strong&gt;, and each step is &lt;em&gt;derived from&lt;/em&gt; the one before it. This is the single most important idea in the lesson, so it's worth saying slowly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with behaviour.&lt;/strong&gt; Before you can know what parts a system needs, you must know what it has to &lt;em&gt;do&lt;/em&gt;. Behaviour is the requirement; everything else serves it. So we draw the flowchart first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let structure fall out of behaviour.&lt;/strong&gt; Once the flowchart exists, the components almost name themselves. The flowchart has a step &lt;em&gt;"check the message is valid"&lt;/em&gt; — so there must be a part that does validation. It has a step &lt;em&gt;"save the message"&lt;/em&gt; — so there must be a part that handles storage. You don't &lt;em&gt;invent&lt;/em&gt; the structure; you &lt;strong&gt;read it off&lt;/strong&gt;the behaviour. Each thing the app must do implies a part that does it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detail the interaction inside the structure.&lt;/strong&gt; Now that you know the parts, you can zoom into one slice of the action and show the precise back-and-forth between them — which component sends what message to which other component, and in what order. The sequence diagram lives &lt;em&gt;inside&lt;/em&gt; the structure the previous step produced.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each diagram is the &lt;em&gt;input&lt;/em&gt; to the next. That's why the order can't be shuffled. Trying to design components before knowing the required behaviour means guessing — and guessed structure is the kind of mistake that's expensive to discover later. Behaviour-first isn't a stylistic preference; it's how you keep every later decision &lt;em&gt;grounded in something real&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The thread you'll be able to trace
&lt;/h3&gt;

&lt;p&gt;Because each diagram derives from the last, something valuable happens: you can follow a single feature &lt;strong&gt;all the way through&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The use case &lt;em&gt;"Post a message"&lt;/em&gt; will appear three times in this lesson — once as a flow of steps, once as a set of components, once as a conversation between them. And crucially, you'll be able to point at any element in any diagram and trace it back. &lt;em&gt;"This validation step in the flowchart"&lt;/em&gt; becomes &lt;em&gt;"this validator block in the functional diagram"_becomes &lt;/em&gt;"this &lt;code&gt;validate()&lt;/code&gt; message in the sequence diagram."_ Same idea, three depths of focus.&lt;/p&gt;

&lt;p&gt;That property has a name — &lt;strong&gt;traceability&lt;/strong&gt; — and it is the quiet engine of good engineering. When you can trace a feature from "what the user wanted" down to "the specific parts that deliver it," nothing falls through the cracks. When you can't, things do.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The takeaway.&lt;/strong&gt; Three diagrams, one progression: &lt;strong&gt;what it does → what it's made of → how the parts interact.&lt;/strong&gt; Each is a different lens on the same &lt;code&gt;Bird&lt;/code&gt; app, and each is derived from the one before. Hold onto that shape — the rest of the lesson simply walks it, one lens at a time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Closing this lesson's opening half
&lt;/h3&gt;

&lt;p&gt;That completes the &lt;strong&gt;framing half of Lesson 1&lt;/strong&gt;. You now have everything you need before the hands-on diagramming begins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a vague wish turned into a &lt;strong&gt;bounded project&lt;/strong&gt; — the &lt;code&gt;Bird&lt;/code&gt; app, three use cases, one worked example;&lt;/li&gt;
&lt;li&gt;a clear understanding of &lt;strong&gt;why blueprints matter&lt;/strong&gt; — they are the cheapest place to be wrong;&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;three-lens mental model&lt;/strong&gt; — behaviour, structure, interaction — and the reason its order is fixed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What comes next.&lt;/strong&gt; That brings &lt;strong&gt;Lesson 1&lt;/strong&gt; to a close. You haven't drawn anything yet — and that's exactly right. A diagram drawn before the project is bounded and the lenses are understood is a diagram you'll redraw. &lt;strong&gt;&lt;a href="https://dev.to/eugene-zimin/the-blueprint-beneath-the-blueprint-designing-data-model-and-choosing-its-database-3bhl"&gt;Lesson 2&lt;/a&gt;&lt;/strong&gt; is where the building starts: first we define the &lt;strong&gt;data model&lt;/strong&gt; — the precise structure of the information &lt;code&gt;Bird&lt;/code&gt; must store and pass between its components — and then we return to the three lenses, drawing the flowchart, functional diagram, and sequence diagram for &lt;em&gt;"Post a message"&lt;/em&gt; on top of that data model. The framing you've just finished is what makes those diagrams quick to draw and hard to get wrong.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>tutorial</category>
      <category>development</category>
      <category>webdev</category>
    </item>
    <item>
      <title>API Authentication: Part III. JWT Tokens</title>
      <dc:creator>Eugene Zimin</dc:creator>
      <pubDate>Sun, 17 May 2026 02:55:46 +0000</pubDate>
      <link>https://dev.to/eugene-zimin/api-authentication-part-iii-jwt-tokens-3c6a</link>
      <guid>https://dev.to/eugene-zimin/api-authentication-part-iii-jwt-tokens-3c6a</guid>
      <description>&lt;h2&gt;
  
  
  Why API Keys Aren't Always Enough
&lt;/h2&gt;

&lt;p&gt;In Part II we saw that an API key is essentially a long, secret password your software shows to a server. It works, but it has a hidden cost: every time the key is used, the server must look it up in a database to find out what the key is allowed to do, whether it has expired, and whether it has been switched off. A &lt;strong&gt;JSON Web Token (JWT)&lt;/strong&gt; removes that lookup by carrying all of that information &lt;em&gt;inside the token itself&lt;/em&gt;. This article explains the problem JWT solves and shows where it sits in the larger story of web authentication.&lt;/p&gt;

&lt;p&gt;Part I covered Basic Authentication — sending a username and password with every request. Part II covered API keys — replacing that reusable password with a single opaque secret string that identifies an application rather than a person.&lt;/p&gt;

&lt;p&gt;Both approaches share a quiet assumption: &lt;strong&gt;the server already knows things about the credential, and it has to go and remember them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think of an API key as a numbered coat-check ticket. The ticket itself tells you nothing — it's just a number. To find out what the ticket entitles you to, the attendant has to walk into the back room, find the matching record, and read it. The ticket is meaningless without the back room.&lt;/p&gt;

&lt;p&gt;That "back room" is a database, and consulting it is called a &lt;strong&gt;database roundtrip&lt;/strong&gt; — the server pauses, sends a question to the database, and waits for an answer before it can continue.&lt;/p&gt;

&lt;h3&gt;
  
  
  The hidden cost of a roundtrip
&lt;/h3&gt;

&lt;p&gt;For most applications, one database lookup per request is perfectly fine. But consider what the server actually has to check each time an API key arrives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Permissions&lt;/strong&gt; — what is this key allowed to do?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiry&lt;/strong&gt; — is this key still valid, or has it passed its end date?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status&lt;/strong&gt; — has someone revoked or suspended this key?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three answers live in the database, not in the key. So &lt;em&gt;every single request&lt;/em&gt; triggers a roundtrip before any real work happens.&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%2Fhs97n2viujir5cv68ouf.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%2Fhs97n2viujir5cv68ouf.png" alt="Figure 1. API Keys and Database Roundtrips" width="800" height="383"&gt;&lt;/a&gt; Figure 1. API Keys and Database Roundtrips&lt;/p&gt;

&lt;p&gt;This becomes a problem at scale in two specific situations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;High request volume.&lt;/strong&gt; A popular API might handle thousands of requests per second. Thousands of identity lookups per second is real, measurable load — and load that does nothing for the user except verify who they are.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Distributed systems.&lt;/strong&gt; Modern applications are often split into many small, independent services (commonly called &lt;strong&gt;microservices&lt;/strong&gt; — a single application built as a collection of small, separately running programs that talk to each other). If a request passes through five services, and each one independently re-checks the caller's identity against the database, that's five roundtrips for one user action. The database becomes a bottleneck that every service depends on.&lt;/p&gt;&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%2Fpu9j54ebe03dhjpg5qzd.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%2Fpu9j54ebe03dhjpg5qzd.png" alt="Figure 2. API Key Validation Scaling Problem" width="800" height="453"&gt;&lt;/a&gt; Figure 2. API Key Validation Scaling Problem&lt;/p&gt;

&lt;p&gt;The deeper issue is &lt;strong&gt;state&lt;/strong&gt;. A server that must consult a database to understand a credential is a &lt;strong&gt;stateful&lt;/strong&gt; server — it cannot make a decision on its own. It always needs the back room.&lt;/p&gt;

&lt;h3&gt;
  
  
  The core idea behind JWT
&lt;/h3&gt;

&lt;p&gt;JWT flips the model. Instead of handing the server a meaningless ticket and forcing it to look up the details, JWT hands the server a ticket that &lt;em&gt;has the details printed on it&lt;/em&gt; — and stamped in a way that proves the printing wasn't forged.&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%2Fxpyq7ezp1jhj6s4sxrp6.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%2Fxpyq7ezp1jhj6s4sxrp6.png" alt="Figure 3. JWT and Stateless Validation" width="800" height="456"&gt;&lt;/a&gt; Figure 3. JWT and Stateless Validation&lt;/p&gt;

&lt;p&gt;To stay with the analogy: a JWT is less like a coat-check number and more like a &lt;strong&gt;boarding pass&lt;/strong&gt;. A boarding pass already states your name, your flight, your seat, and your boarding time, right there on the paper. The gate agent doesn't phone headquarters to look you up — they read the pass and check that it's genuine. The information travels &lt;em&gt;with&lt;/em&gt; the traveler.&lt;/p&gt;

&lt;p&gt;This property has a precise name. A server that can validate a token using only the token itself (plus a verification key it already holds) is &lt;strong&gt;stateless&lt;/strong&gt; — it holds no per-request memory and needs no back room. We will unpack exactly &lt;em&gt;how&lt;/em&gt; a JWT proves it hasn't been forged later, when we open up its three-part structure. For now, the one idea to carry forward is the trade:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An API key keeps the metadata in the database and gives you a pointer to it. A JWT puts the metadata in the token and gives you a way to trust it.&lt;/p&gt;
&lt;/blockquote&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%2Fl7cb5fqfnxqp9br7fu04.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%2Fl7cb5fqfnxqp9br7fu04.png" alt="Figure 4. API Keys vs. JWT comparison" width="799" height="402"&gt;&lt;/a&gt; Figure 4. API Keys vs. JWT comparison&lt;/p&gt;

&lt;p&gt;That trade is powerful, but — as Part III will explore honestly — it is not free. Information printed on a token cannot be un-printed, which creates a genuine difficulty when you need to cancel a token early.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Few Words of History
&lt;/h2&gt;

&lt;p&gt;Every technology arrives at a particular moment for a particular reason. JWT is no exception — it appeared just as the web was shifting away from a model that had quietly dominated for over a decade.&lt;/p&gt;

&lt;h3&gt;
  
  
  The world before: the session cookie
&lt;/h3&gt;

&lt;p&gt;For most of the 2000s, web authentication worked through &lt;strong&gt;server-side sessions&lt;/strong&gt;. When you logged in, the server created a record of your session in its own memory or database and handed your browser a small identifier — a &lt;strong&gt;session cookie&lt;/strong&gt;. On every later request, your browser sent that cookie back, and the server looked it up to remember who you were.&lt;/p&gt;

&lt;p&gt;This is the same coat-check pattern from the previous session above: the cookie is a meaningless number, and the server holds all the real information. It worked well when a website was a single server. But it tied every logged-in user to a specific machine's memory. Spread your traffic across many servers, and you hit a problem — a user logged in on &lt;strong&gt;Server A&lt;/strong&gt; is a stranger to &lt;strong&gt;Server B&lt;/strong&gt;, because &lt;strong&gt;Server B&lt;/strong&gt; never created that session record.&lt;/p&gt;

&lt;h3&gt;
  
  
  The pressure that created JWT
&lt;/h3&gt;

&lt;p&gt;Two trends in the early 2010s made server-side sessions increasingly awkward.&lt;/p&gt;

&lt;p&gt;First, applications stopped being single servers. They were spread across fleets of machines, and increasingly split into &lt;strong&gt;microservices&lt;/strong&gt; — many small programs, each handling one job. A shared, central session store became a bottleneck that every service had to consult.&lt;/p&gt;

&lt;p&gt;Second, the &lt;em&gt;clients&lt;/em&gt; changed. The web was no longer just browsers loading full pages. It was single-page applications (SPAs) — websites that load once and then behave like desktop apps — and native mobile apps, often talking to APIs hosted on entirely different domains. Cookies, which are tightly bound to a single domain by design, fit this new world poorly.&lt;/p&gt;

&lt;p&gt;The industry needed a credential that any server could verify on its own, without a shared session store, and that wasn't anchored to one domain. The answer was to make the token &lt;em&gt;carry its own proof of identity&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  RFC 7519: the standard
&lt;/h3&gt;

&lt;p&gt;The work happened inside the &lt;strong&gt;IETF&lt;/strong&gt; (the Internet Engineering Task Force, the body that standardizes the protocols underlying the internet) as part of its OAuth working group — the same group designing the next generation of authorization standards. They needed a compact, secure token format to pass identity information around, and JWT was built to fill that gap.&lt;/p&gt;

&lt;p&gt;The result was published in &lt;strong&gt;May 2015 as &lt;a href="https://datatracker.ietf.org/doc/html/rfc7519" rel="noopener noreferrer"&gt;RFC 7519&lt;/a&gt;&lt;/strong&gt; — the formal specification that defines what a JSON Web Token is. An &lt;strong&gt;RFC&lt;/strong&gt; ("Request for Comments") is the document format the IETF uses for internet standards; despite the tentative-sounding name, a published RFC is the authoritative definition.&lt;/p&gt;

&lt;p&gt;A point worth noting: JWT was never a lone invention. It is the centerpiece of a small family of related standards — collectively called &lt;strong&gt;JOSE&lt;/strong&gt; (JavaScript Object Signing and Encryption) — that also define how to sign tokens (JWS), encrypt them (JWE), and represent cryptographic keys (JWK). &lt;/p&gt;

&lt;p&gt;For most practical purposes, though, "JWT" is the term everyone uses, and it's the one we'll use here.&lt;/p&gt;

&lt;h3&gt;
  
  
  From standard to ubiquity
&lt;/h3&gt;

&lt;p&gt;A specification only matters if people adopt it, and JWT's adoption was unusually fast. The identity company &lt;strong&gt;&lt;a href="https://auth0.com" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt;&lt;/strong&gt;, founded in 2013, built much of its product and developer education around JWT and promoted the format heavily — including &lt;code&gt;jwt.io&lt;/code&gt;, a free online debugger that let developers paste in a token and see its decoded contents instantly. For a great many engineers, that tool was their first hands-on encounter with the format.&lt;/p&gt;

&lt;p&gt;From there, JWT spread through the ecosystem. It became the default token format for &lt;strong&gt;&lt;a href="https://openid.net" rel="noopener noreferrer"&gt;OpenID Connect&lt;/a&gt;&lt;/strong&gt; — the identity layer built on top of &lt;a href="https://datatracker.ietf.org/doc/html/rfc6749" rel="noopener noreferrer"&gt;OAuth 2.0&lt;/a&gt;, and the subject we'll reach later in this series. Cloud platforms adopted it for service-to-service authentication. Web frameworks in nearly every programming language shipped libraries to create and verify it. Within a few years of RFC 7519, JWT had moved from a new proposal to a default assumption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the history matters
&lt;/h3&gt;

&lt;p&gt;This background isn't trivia — it explains the &lt;em&gt;shape&lt;/em&gt; of the technology you're about to take apart. JWT looks the way it does because it was designed for a specific world: distributed systems with no shared memory, and clients scattered across domains and devices.&lt;/p&gt;

&lt;p&gt;That origin also explains its central trade-off. JWT was built to let a server make a decision &lt;em&gt;on its own&lt;/em&gt;, without calling back to a central store. That independence is exactly the feature that makes JWT fast and scalable — and, as we'll see, exactly the feature that makes a token hard to cancel once it's been issued. The strength and the weakness are the same design decision, viewed from two sides.&lt;/p&gt;

&lt;p&gt;With that context in place, we can now open up an actual token and see what those three dot-separated parts really contain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Anatomy of a JWT
&lt;/h2&gt;

&lt;p&gt;This is the heart of subject. Once you can see what a JWT actually &lt;strong&gt;&lt;em&gt;is&lt;/em&gt;&lt;/strong&gt;, every later topic — claims, signatures, validation, security — becomes far easier to follow. So we'll take a real token apart, piece by piece.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three parts, two dots
&lt;/h3&gt;

&lt;p&gt;A JWT, when written out, looks like a long, slightly intimidating string of gibberish:&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="err"&gt;eyJhbGciOiJIUzI&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;NiIsInR&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="err"&gt;cCI&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="err"&gt;IkpXVCJ&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="err"&gt;.eyJzdWIiOiIxMjM&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;NSIsIm&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="err"&gt;hbWUiOiJBZGEifQ.dBjftJeZ&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="err"&gt;CVP-mB&lt;/span&gt;&lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="err"&gt;K&lt;/span&gt;&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="err"&gt;uhbUJU&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;p&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;r_wW&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;gFWFOEjXk&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks random. It isn't. Look closely and you'll spot &lt;strong&gt;two dots&lt;/strong&gt; dividing it into &lt;strong&gt;three parts&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;header  .  payload  .  signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That structure never changes. Every JWT, everywhere, is exactly three parts separated by dots:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;header&lt;/strong&gt; — describes the token itself.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;payload&lt;/strong&gt; — carries the actual information.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;signature&lt;/strong&gt; — proves the first two parts haven't been tampered with.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The boarding-pass analogy from the first section holds up neatly here. The header is the small print at the top that says what kind of document this is. The payload is the part you actually care about — your name, your flight, your seat. The signature is the official stamp that proves the pass is genuine and not something printed at home.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it looks like gibberish: Base64URL
&lt;/h3&gt;

&lt;p&gt;The reason a JWT looks unreadable is not encryption. It is &lt;strong&gt;encoding&lt;/strong&gt; — and the distinction matters.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Encryption&lt;/strong&gt; scrambles data so that &lt;em&gt;no one&lt;/em&gt; can read it without a secret key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encoding&lt;/strong&gt; simply rewrites data into a different alphabet so it can travel safely. Anyone can reverse it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A JWT's header and payload are merely &lt;strong&gt;encoded&lt;/strong&gt;, not encrypted. They use a scheme called &lt;strong&gt;Base64URL&lt;/strong&gt; — a way of representing data using only letters, digits, and a couple of symbols that are safe to put inside a web address (a URL).&lt;/p&gt;

&lt;p&gt;This leads to the single most important — and most misunderstood — fact about JWTs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The contents of a JWT are not secret.&lt;/strong&gt; Anyone who holds the token can decode the header and payload and read everything inside. The signature stops people from &lt;em&gt;changing&lt;/em&gt; a token; it does nothing to &lt;em&gt;hide&lt;/em&gt; it.&lt;/p&gt;
&lt;/blockquote&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%2F1zuqexgjjztg0wdepb4t.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%2F1zuqexgjjztg0wdepb4t.png" alt="Figure 5. JWT Decoding vs. Hiding Secrets" width="800" height="457"&gt;&lt;/a&gt; Figure 5. JWT Decoding vs. Hiding Secrets&lt;/p&gt;

&lt;p&gt;We'll return to the security consequences of this later. For now, just hold onto it: a JWT protects against forgery, not against reading. Never put a password, a credit-card number, or any other secret inside it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 1: The header
&lt;/h3&gt;

&lt;p&gt;Take the first segment of our example token and reverse the Base64URL encoding, and you get a small block of &lt;strong&gt;JSON&lt;/strong&gt;— JavaScript Object Notation, the simple, human-readable &lt;code&gt;key: value&lt;/code&gt; format used everywhere on the modern web:&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;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HS256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"typ"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JWT"&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 header is metadata &lt;em&gt;about the token&lt;/em&gt;. It answers two questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;alg&lt;/code&gt;&lt;/strong&gt; ("algorithm") — how the signature was created. Here, &lt;code&gt;HS256&lt;/code&gt;. We'll examine the algorithm choices in detail in part of the article; for now, treat it as a label naming the method used to make the stamp.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;typ&lt;/code&gt;&lt;/strong&gt; ("type") — what kind of object this is. For a JWT, this is simply &lt;code&gt;"JWT"&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The header is short, and it's mostly there so the receiving server knows &lt;em&gt;how&lt;/em&gt; to check the signature later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 2: The payload
&lt;/h3&gt;

&lt;p&gt;Decode the second segment and you get another block of JSON — and this is the part that does the real work:&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;"12345"&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="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ada"&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 payload carries the &lt;strong&gt;claims&lt;/strong&gt; — the individual statements the token is making. The word is well chosen: each entry is a &lt;em&gt;claim&lt;/em&gt;, an assertion that "this is true." Here the token claims two things: the subject (&lt;code&gt;sub&lt;/code&gt;) of this token is user &lt;code&gt;12345&lt;/code&gt;, and that user's name is &lt;code&gt;Ada&lt;/code&gt;. The payload is where identity, permissions, expiry times, and your own custom data live. &lt;/p&gt;

&lt;h3&gt;
  
  
  Part 3: The signature
&lt;/h3&gt;

&lt;p&gt;The third segment is the only part that is genuinely cryptographic — and it's the part that makes a JWT trustworthy.&lt;/p&gt;

&lt;p&gt;Here is the problem the signature solves. We just established that anyone holding a token can read the payload. But could they also &lt;em&gt;change&lt;/em&gt; it — swap &lt;code&gt;"sub": "12345"&lt;/code&gt; for &lt;code&gt;"sub": "99999"&lt;/code&gt; and impersonate another user? The answer is &lt;strong&gt;"YES"&lt;/strong&gt;. Does it stay hidden? And the most important answer is here - &lt;strong&gt;"NO"&lt;/strong&gt;, as the signature is what makes that attack fail because it becomes easily recognizable and server knows how to react on it.&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%2Fhymigr4td0c8azd061vq.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%2Fhymigr4td0c8azd061vq.png" alt="Figure 6. JWT Forgery Protection - The Signature" width="800" height="457"&gt;&lt;/a&gt;Figure 6. JWT Forgery Protection - The Signature&lt;/p&gt;

&lt;p&gt;When the server first creates the token, it performs a calculation. It takes the encoded header and the encoded payload, joins them with a dot, and feeds that — together with a &lt;strong&gt;secret key that only the server knows&lt;/strong&gt; — into a one-way mathematical function. For our &lt;code&gt;HS256&lt;/code&gt; example, that function is &lt;strong&gt;HMAC-SHA256&lt;/strong&gt;. The output is the signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signature = HMAC-SHA256(
    base64url(header) + "." + base64url(payload),
    secret
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two properties of this function are what make the whole scheme work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It depends on every input.&lt;/strong&gt; Change a single character of the header or payload, and the output is completely different.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It can't be reversed or faked without the secret.&lt;/strong&gt; Knowing the inputs but not the secret, there is no practical way to compute the correct signature.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So when an attacker edits the payload to say &lt;code&gt;"sub": "99999"&lt;/code&gt;, the signature attached to the token no longer matches the modified content. When the server recomputes the signature from the tampered payload and compares it to the one in the token, the two don't match — and the token is rejected. Because the attacker doesn't have the secret key, they can't produce a fresh, valid signature for their forged payload either.&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%2Fnbcx81judko6uvdi8xjy.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%2Fnbcx81judko6uvdi8xjy.png" alt="Figure 7. JWT Signature- The Key to Integrity and Honesty" width="" height=""&gt;&lt;/a&gt;Figure 7. JWT Signature- The Key to Integrity and Honesty&lt;/p&gt;

&lt;p&gt;And this is the core idea worth carrying out of this section:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The signature doesn't keep a JWT &lt;em&gt;private&lt;/em&gt;. It keeps a JWT &lt;em&gt;honest&lt;/em&gt;. It guarantees that the header and payload are exactly what the issuing server wrote, untouched since.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Building one by hand
&lt;/h3&gt;

&lt;p&gt;The best way to dispel the sense that this is magic is to build a token with no library at all — just the standard tools any programming language provides. Here it is in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;base64url_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Standard Base64, then made URL-safe: trailing '=' padding removed.
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_jwt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. The header: declare the algorithm and type.
&lt;/span&gt;    &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HS256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;typ&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JWT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. Encode the header and payload as Base64URL.
&lt;/span&gt;    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;base64url_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;base64url_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. Sign "header.payload" with the secret using HMAC-SHA256.
&lt;/span&gt;    &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# 4. Join all three parts with dots.
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;base64url_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_jwt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sub&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12345&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ada&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-secret-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the &lt;em&gt;entire&lt;/em&gt; mechanism. There is nothing hidden. A JWT is two pieces of JSON, encoded for safe transport, plus a signature computed from them. The four numbered steps in the code are the whole story.&lt;/p&gt;

&lt;h3&gt;
  
  
  The production path: use a library
&lt;/h3&gt;

&lt;p&gt;Building a token by hand is the right way to &lt;em&gt;understand&lt;/em&gt; JWTs. It is the wrong way to &lt;em&gt;use&lt;/em&gt; them in real software.&lt;/p&gt;

&lt;p&gt;In production, always reach for a well-maintained, audited library — &lt;code&gt;PyJWT&lt;/code&gt; or &lt;code&gt;python-jose&lt;/code&gt; in Python, and direct equivalents in every other major language. The same task becomes a single call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;  &lt;span class="c1"&gt;# the PyJWT library
&lt;/span&gt;
&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sub&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12345&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ada&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-secret-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;algorithm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HS256&lt;/span&gt;&lt;span class="sh"&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 isn't laziness — it's safety. The hand-written version above is correct for &lt;em&gt;creating&lt;/em&gt; tokens, but &lt;em&gt;verifying&lt;/em&gt; them safely is full of subtle traps. A naive verifier can be tricked into accepting forged tokens, expired tokens, or tokens it should never have trusted. Mature libraries have already absorbed those lessons; your own code, written from scratch, has not.&lt;/p&gt;

&lt;p&gt;So build one by hand once, to see that the magic is just arithmetic. Then never do it again in production.&lt;/p&gt;

&lt;p&gt;With the structure of a token clear, we can turn to its most important part in detail — the payload, and the claims it carries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claims: What a Token Actually Says
&lt;/h2&gt;

&lt;p&gt;In previous section we opened up a JWT and found the payload — the middle segment carrying the token's real content. Each piece of information in that payload is called a &lt;strong&gt;claim&lt;/strong&gt;. This section is about what those claims are, which ones the standard defines for you, and which ones you create yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  A claim is just a statement
&lt;/h3&gt;

&lt;p&gt;The terminology sounds formal, but the idea is plain. A &lt;strong&gt;claim&lt;/strong&gt; is a single statement the token makes about its subject — one &lt;code&gt;key: value&lt;/code&gt; pair in the payload's JSON. The token "claims" these things are true, and the signature is what makes that claim trustworthy.&lt;/p&gt;

&lt;p&gt;A payload is simply a collection of these statements:&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;"auth.myapp.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;"12345"&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="s2"&gt;"api.myapp.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;"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;1716120000&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;1716123600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"editor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant_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;"acme-corp"&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;Some of those keys — &lt;code&gt;iss&lt;/code&gt;, &lt;code&gt;sub&lt;/code&gt;, &lt;code&gt;aud&lt;/code&gt;, &lt;code&gt;iat&lt;/code&gt;, &lt;code&gt;exp&lt;/code&gt; — are part of the JWT standard and mean the same thing everywhere. Others — &lt;code&gt;role&lt;/code&gt;, &lt;code&gt;tenant_id&lt;/code&gt; — are invented by the application. That split is the central idea of this section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Registered claims: the standard vocabulary
&lt;/h3&gt;

&lt;p&gt;RFC 7519 defines a small set of &lt;strong&gt;registered claims&lt;/strong&gt;. These are not required — a JWT is free to omit any of them — but if you &lt;em&gt;do&lt;/em&gt; use them, you must use them with the meaning the standard assigns. They have short, three-letter names to keep tokens compact.&lt;/p&gt;

&lt;p&gt;There are seven worth knowing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iss&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Issuer&lt;/td&gt;
&lt;td&gt;Who created and signed this token.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sub&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Subject&lt;/td&gt;
&lt;td&gt;Who or what the token is about — typically the user ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aud&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Audience&lt;/td&gt;
&lt;td&gt;Who the token is &lt;em&gt;intended for&lt;/em&gt; — which service should accept it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;exp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Expiration Time&lt;/td&gt;
&lt;td&gt;The moment after which the token must be rejected.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nbf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not Before&lt;/td&gt;
&lt;td&gt;The moment before which the token is not yet valid.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Issued At&lt;/td&gt;
&lt;td&gt;The moment the token was created.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jti&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JWT ID&lt;/td&gt;
&lt;td&gt;A unique identifier for this specific token.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few of these deserve a closer look, because they do more than they first appear to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;iss&lt;/code&gt; (Issuer)&lt;/strong&gt; identifies the authority that minted the token — usually an authentication server like &lt;code&gt;auth.myapp.com&lt;/code&gt;. A receiving service checks this so it only trusts tokens from a source it recognizes, and ignores tokens minted by anyone else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;aud&lt;/code&gt; (Audience)&lt;/strong&gt; is the one most often misunderstood, and it matters for security. It names the intended &lt;em&gt;recipient&lt;/em&gt;. If your authentication server issues tokens for several different services, the &lt;code&gt;aud&lt;/code&gt; claim lets each service confirm "this token was meant for &lt;em&gt;me&lt;/em&gt;." A token minted for the billing service should be refused by the email service, even though both trust the same issuer. Skipping this check is a real vulnerability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;jti&lt;/code&gt; (JWT ID)&lt;/strong&gt; is a unique serial number for one individual token. On its own it does little. Its importance shows up later: when you need to &lt;em&gt;cancel&lt;/em&gt; a specific token before it expires, the &lt;code&gt;jti&lt;/code&gt; is the handle you use to do it. That's the revocation problem, and &lt;code&gt;jti&lt;/code&gt; is the thread that connects to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The temporal claims: &lt;code&gt;iat&lt;/code&gt;, &lt;code&gt;nbf&lt;/code&gt;, and &lt;code&gt;exp&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Three of the registered claims — &lt;code&gt;iat&lt;/code&gt;, &lt;code&gt;nbf&lt;/code&gt;, and &lt;code&gt;exp&lt;/code&gt; — are about &lt;em&gt;time&lt;/em&gt;, and together they give a token a lifespan. They are the mechanism behind one of JWT's most important properties: a token that automatically stops working.&lt;/p&gt;

&lt;p&gt;Laid out on a timeline, they mark out a window of validity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   iat            nbf              now                      exp
    │              │                │                        │
    ●──────────────●════════════════●════════════════════════●─────────►  time
  issued       valid from      this moment              expires after
                 │◄───────── token is VALID here ───────────►│
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Figure 8. Timeline for JWT claims&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;iat&lt;/code&gt; (Issued At)&lt;/strong&gt; is the token's birth timestamp — when it was created.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;nbf&lt;/code&gt; (Not Before)&lt;/strong&gt; is the moment the token &lt;em&gt;starts&lt;/em&gt; being valid. Usually this is the same as &lt;code&gt;iat&lt;/code&gt;, but it can be set in the future to issue a token now that only "switches on" later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;exp&lt;/code&gt; (Expiration Time)&lt;/strong&gt; is the moment the token &lt;em&gt;stops&lt;/em&gt; being valid. After this instant, every correct server must reject it, no matter what else the token says.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;exp&lt;/code&gt; claim is what gives a JWT its self-limiting nature. An API key, by contrast, tends to live until a human remembers to revoke it. A JWT carries its own deadline. Set &lt;code&gt;exp&lt;/code&gt; to 15 minutes after &lt;code&gt;iat&lt;/code&gt;, and the token simply expires 15 minutes later — no database, no cleanup job, no human action. This is central to managing the risk of a stolen token.&lt;/p&gt;

&lt;p&gt;One technical detail: these timestamps are stored as &lt;strong&gt;Unix time&lt;/strong&gt; — the number of seconds since midnight UTC on January 1, 1970. That's why &lt;code&gt;exp&lt;/code&gt; appears as a large integer like &lt;code&gt;1716123600&lt;/code&gt; rather than a human-readable date. It's a universal, timezone-free way to pin down an exact instant, and every JWT library converts it for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom claims: your own data
&lt;/h3&gt;

&lt;p&gt;The registered claims cover identity and timing. They say nothing about what a user is &lt;em&gt;allowed to do&lt;/em&gt;, because that is specific to your application. For everything else, you add &lt;strong&gt;custom claims&lt;/strong&gt; — &lt;code&gt;key: value&lt;/code&gt; pairs you define yourself, with whatever names and meanings you choose.&lt;/p&gt;

&lt;p&gt;Custom claims are where JWT delivers on the promise from the section above — moving metadata &lt;em&gt;into the token&lt;/em&gt; so the server doesn't need a database lookup. Typical examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;role&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;permissions&lt;/code&gt;&lt;/strong&gt; — what the user is authorized to do (&lt;code&gt;"editor"&lt;/code&gt;, &lt;code&gt;"admin"&lt;/code&gt;, or a list like &lt;code&gt;["read:invoices", "write:invoices"]&lt;/code&gt;). The receiving service reads this straight from the token and decides what to allow — no roundtrip required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tenant_id&lt;/code&gt;&lt;/strong&gt; — in applications that serve many separate organizations from one system, this identifies which organization the user belongs to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;email&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;name&lt;/code&gt;&lt;/strong&gt; — basic profile fields, included to save a lookup when displaying the user's identity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is exactly the design that makes a server &lt;strong&gt;stateless&lt;/strong&gt;: the answer to "who is this, and what may they do?" travels inside the request itself.&lt;/p&gt;

&lt;p&gt;Two cautions, both flowing directly from facts already established.&lt;/p&gt;

&lt;p&gt;First — and this bears repeating because it is the most common JWT mistake — &lt;strong&gt;the payload is not secret.&lt;/strong&gt; Anyone holding the token can decode and read it. So custom claims may contain a user's role; they must never contain anything sensitive — no passwords, no API secrets, no private personal data.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;keep the payload small.&lt;/strong&gt; A JWT travels with &lt;em&gt;every single request&lt;/em&gt; to your API. Every claim you add makes every request slightly larger. Stick to what the receiving service genuinely needs to make a decision; resist the temptation to pack a user's entire profile into the token.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoiding name collisions
&lt;/h3&gt;

&lt;p&gt;If your application invents a custom claim called &lt;code&gt;role&lt;/code&gt;, and some other system your token passes through also uses &lt;code&gt;role&lt;/code&gt; to mean something different, the two meanings collide. The JWT standard's recommended fix is to &lt;strong&gt;namespace&lt;/strong&gt; custom claims — prefix them with a URL you control, so the name is globally unique:&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;"12345"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"https://myapp.com/role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"editor"&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 URL doesn't have to point anywhere real; it's used purely as a unique prefix. For a closed system where you control every service, plain names like &lt;code&gt;role&lt;/code&gt; are common and perfectly workable. For tokens that travel between organizations, namespacing prevents a class of subtle, hard-to-diagnose bugs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The payload, in summary
&lt;/h3&gt;

&lt;p&gt;A JWT's payload is a set of claims — statements the token asserts and the signature makes trustworthy. Some claims are &lt;strong&gt;registered&lt;/strong&gt; by the standard: &lt;code&gt;iss&lt;/code&gt;, &lt;code&gt;sub&lt;/code&gt;, and &lt;code&gt;aud&lt;/code&gt; answer &lt;em&gt;who&lt;/em&gt;; &lt;code&gt;iat&lt;/code&gt;, &lt;code&gt;nbf&lt;/code&gt;, and &lt;code&gt;exp&lt;/code&gt; answer &lt;em&gt;when&lt;/em&gt;; &lt;code&gt;jti&lt;/code&gt; gives the token a unique name. The rest are &lt;strong&gt;custom claims&lt;/strong&gt; you define, and they are how identity and permissions ride along inside the request, sparing the server a database lookup.&lt;/p&gt;

&lt;p&gt;We now know what a token &lt;em&gt;says&lt;/em&gt;. The next question is &lt;em&gt;how&lt;/em&gt; it proves it — which means looking closely at the signature, and the different algorithms used to create it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signature Algorithms: HS256, RS256, and ES256
&lt;/h2&gt;

&lt;p&gt;Above we established what the signature &lt;em&gt;does&lt;/em&gt;: it makes a token honest, guaranteeing the header and payload haven't been altered since the issuer wrote them. We used &lt;code&gt;HS256&lt;/code&gt; as the example and treated it as a black box. Now we open the box — because the choice of signing algorithm is one of the most consequential decisions you'll make when building a real system, and it turns entirely on a single question: &lt;strong&gt;who is allowed to verify the token?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Two families: symmetric and asymmetric
&lt;/h3&gt;

&lt;p&gt;Every JWT signing algorithm belongs to one of two families. The difference between them is the difference between a shared password and a wax seal.&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%2Fyjjy1odpu2fbjc4kzx7x.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%2Fyjjy1odpu2fbjc4kzx7x.png" alt="Figure 9. JWT Signing - Symmetric vs. Asymmetric" width="800" height="454"&gt;&lt;/a&gt; Figure 9. JWT Signing - Symmetric vs. Asymmetric&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;symmetric&lt;/strong&gt; algorithm uses &lt;strong&gt;one secret key for both jobs&lt;/strong&gt; — creating the signature and checking it. The same key signs and verifies. It's like a password shared between two parties: whoever knows it can both lock and unlock.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;asymmetric&lt;/strong&gt; algorithm uses &lt;strong&gt;a pair of mathematically linked keys&lt;/strong&gt;: a &lt;strong&gt;private key&lt;/strong&gt; and a &lt;strong&gt;public key&lt;/strong&gt;. The private key creates signatures and is guarded closely. The public key only &lt;em&gt;verifies&lt;/em&gt; signatures and can be shared freely — handed to anyone, posted publicly — without weakening anything. It's like a wax seal: the issuer owns the unique stamp (the private key), but anyone with a picture of the seal (the public key) can recognize a genuine one. Holding the public key lets you &lt;em&gt;check&lt;/em&gt; a seal but never &lt;em&gt;forge&lt;/em&gt; one.&lt;/p&gt;

&lt;p&gt;That single distinction — one shared key versus a public/private pair — drives everything that follows.&lt;/p&gt;

&lt;h3&gt;
  
  
  HS256: the symmetric option
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;HS256&lt;/strong&gt; stands for &lt;strong&gt;HMAC using SHA-256&lt;/strong&gt;. It is the symmetric choice, and it's the algorithm in every example so far.&lt;/p&gt;

&lt;p&gt;With HS256, one secret string both signs and verifies tokens. The server that issues a token uses the secret to compute the signature; the server that receives a token uses &lt;em&gt;the same secret&lt;/em&gt; to recompute the signature and compare. If they match, the token is genuine.&lt;/p&gt;

&lt;p&gt;This is simple, fast, and produces compact signatures. But it carries one unavoidable consequence: &lt;strong&gt;every party that needs to verify a token must hold the secret key.&lt;/strong&gt; And here is the catch — the verifying key and the signing key are the &lt;em&gt;same key&lt;/em&gt;. Any service that can &lt;em&gt;check&lt;/em&gt; a token can therefore also &lt;em&gt;mint&lt;/em&gt; a brand-new token that everyone else will trust.&lt;/p&gt;

&lt;p&gt;In a single, self-contained application, that's fine. The same server issues tokens and checks them. There's one secret, in one place, and no one else needs it.&lt;/p&gt;

&lt;p&gt;The trouble begins when the system grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  RS256: the asymmetric option
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;RS256&lt;/strong&gt; stands for &lt;strong&gt;RSA Signature using SHA-256&lt;/strong&gt;. RSA is a long-established public-key cryptosystem; what matters here is that RS256 is &lt;strong&gt;asymmetric&lt;/strong&gt; — it uses the private/public key pair.&lt;/p&gt;

&lt;p&gt;With RS256, the authentication server holds a &lt;strong&gt;private key&lt;/strong&gt; and uses it — and only it — to sign tokens. It then publishes the matching &lt;strong&gt;public key&lt;/strong&gt; for the world to see. Any other service can take that public key and verify a token's signature. But the public key &lt;em&gt;cannot sign anything&lt;/em&gt;. A service holding only the public key can confirm a token is genuine, yet has no power to forge one.&lt;/p&gt;

&lt;p&gt;That asymmetry solves the exact problem HS256 created.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "aha": why this matters for microservices
&lt;/h3&gt;

&lt;p&gt;Recall the microservices picture from Figure 1 — an application split into many small, independent services. Suppose one of them, the authentication service, issues tokens, and a dozen others need to verify them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With HS256&lt;/strong&gt;, every one of those dozen services must hold the shared secret. That's a real problem on two fronts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A wider attack surface.&lt;/strong&gt; The secret now lives in twelve places instead of one. A breach of _any single service_leaks the key — and with it, the power to forge tokens that all twelve will trust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Misplaced trust.&lt;/strong&gt; Every service that can &lt;em&gt;verify&lt;/em&gt; can also &lt;em&gt;issue&lt;/em&gt;. A minor, low-privilege service now holds the keys to impersonate anyone. That violates a basic security principle: a component should hold only the power it actually needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With RS256&lt;/strong&gt;, the picture changes completely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;private key lives in exactly one place&lt;/strong&gt; — the authentication service. Only it can mint tokens.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;public key is distributed&lt;/strong&gt; to all twelve verifying services. It's not a secret; if one leaks, nothing is compromised, because a public key cannot forge anything.&lt;/li&gt;
&lt;li&gt;Verifying services can do their job — confirming a token is genuine — without ever holding the power to create one.&lt;/li&gt;
&lt;/ul&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%2F9rxdkphjshwg1arb52qk.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%2F9rxdkphjshwg1arb52qk.png" alt="Figure 10. Token Validation in Microservices - Securing the Keys" width="800" height="393"&gt;&lt;/a&gt; Figure 10. Token Validation in Microservices - Securing the Keys&lt;/p&gt;

&lt;p&gt;This is the central insight of the section, and it's worth stating plainly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In a distributed system, use an &lt;strong&gt;asymmetric&lt;/strong&gt; algorithm. It lets you separate the power to &lt;em&gt;issue&lt;/em&gt; tokens from the power to &lt;em&gt;verify&lt;/em&gt; them — concentrating the dangerous capability in one guarded place while distributing the harmless one freely.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  ES256: asymmetric, but leaner
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;ES256&lt;/strong&gt; stands for &lt;strong&gt;ECDSA using SHA-256&lt;/strong&gt;, where ECDSA is the Elliptic Curve Digital Signature Algorithm.&lt;/p&gt;

&lt;p&gt;For decision-making purposes, ES256 belongs in the same category as RS256: it is &lt;strong&gt;asymmetric&lt;/strong&gt;, with a private key for signing and a public key for verifying, and it solves the microservices problem in exactly the same way. The difference is mechanical. ES256 is built on &lt;strong&gt;elliptic-curve cryptography&lt;/strong&gt;, a more modern branch of public-key cryptography that achieves the same security strength with much smaller keys and signatures.&lt;/p&gt;

&lt;p&gt;The practical payoff is &lt;strong&gt;size&lt;/strong&gt;. An RS256 signature is large; an ES256 signature, at comparable security, is substantially smaller. Since a JWT travels with every request, a smaller signature means a smaller token on every call. For a high-traffic API, that adds up.&lt;/p&gt;

&lt;p&gt;The trade-off is maturity and ubiquity. RSA has been in use for decades and is supported absolutely everywhere; elliptic-curve support, while now excellent, is marginally less universal in older systems. In practice, both are solid choices — RS256 is the safe, conventional default, and ES256 is the leaner, more modern alternative.&lt;/p&gt;

&lt;h3&gt;
  
  
  Putting it side by side
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;HS256&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;RS256&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;ES256&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Family&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Symmetric&lt;/td&gt;
&lt;td&gt;Asymmetric&lt;/td&gt;
&lt;td&gt;Asymmetric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Key(s)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One shared secret&lt;/td&gt;
&lt;td&gt;RSA private/public pair&lt;/td&gt;
&lt;td&gt;EC private/public pair&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Who can verify?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Only holders of the secret&lt;/td&gt;
&lt;td&gt;Anyone with the public key&lt;/td&gt;
&lt;td&gt;Anyone with the public key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Can a verifier also forge tokens?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes&lt;/strong&gt; — same key signs and verifies&lt;/td&gt;
&lt;td&gt;No — public key can't sign&lt;/td&gt;
&lt;td&gt;No — public key can't sign&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Signature size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best fit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A single, self-contained service&lt;/td&gt;
&lt;td&gt;Distributed systems / microservices&lt;/td&gt;
&lt;td&gt;Distributed systems where token size matters&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The decision rule is short. If your application is a &lt;strong&gt;single service&lt;/strong&gt; that both issues and verifies its own tokens, &lt;strong&gt;HS256&lt;/strong&gt; is simple, fast, and entirely appropriate — there's no second party, so a shared secret shares nothing. The moment &lt;strong&gt;more than one independent service must verify tokens&lt;/strong&gt;, switch to an asymmetric algorithm: &lt;strong&gt;RS256&lt;/strong&gt; as the well-supported default, &lt;strong&gt;ES256&lt;/strong&gt; when you want smaller tokens and your platform supports it. The question is never "which algorithm is best" in the abstract — it is "who, in my system, needs to verify a token, and should those same parties be able to create one?"&lt;/p&gt;

&lt;p&gt;We now know how a token is signed. But verifying a token correctly involves much more than checking that one signature. The next section lays out the full validation checklist — and shows why skipping any step on it opens a door.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation: The Full Checklist
&lt;/h2&gt;

&lt;p&gt;Here is a mistake that has shipped to production in countless real systems: a developer wires up JWT authentication, confirms the signature checks out, and considers the job done. The signature is valid — so the token is trustworthy. Right?&lt;/p&gt;

&lt;p&gt;Not quite. &lt;strong&gt;A valid signature answers only one question: was this token altered since it was issued?&lt;/strong&gt; It says nothing about &lt;em&gt;when&lt;/em&gt; the token was issued, &lt;em&gt;who&lt;/em&gt; issued it, &lt;em&gt;who&lt;/em&gt; it was meant for, or whether it has been &lt;em&gt;cancelled&lt;/em&gt; in the meantime. A token can have a perfect signature and still be one your server must refuse.&lt;/p&gt;

&lt;p&gt;Proper validation is therefore a &lt;strong&gt;checklist&lt;/strong&gt;, run in order. This section walks through all six steps, mirroring the security rigor of the earlier parts of this series.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the order matters
&lt;/h3&gt;

&lt;p&gt;The steps are sequenced deliberately, and the first one comes first for a reason: &lt;strong&gt;until the signature is fully verified, you cannot trust a single byte of the payload.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every later check — expiry, issuer, audience — reads a value &lt;em&gt;from the payload&lt;/em&gt;. But if the signature hasn't been confirmed, the payload might be an attacker's forgery, and an attacker writes their &lt;code&gt;exp&lt;/code&gt; claim to say whatever they like. Checking expiry on an unverified payload is theatre. So the signature is verified first; only once it passes does the payload become trustworthy enough to inspect.&lt;/p&gt;

&lt;h3&gt;
  
  
  The six-step checklist
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Verify the signature.&lt;/strong&gt; Recompute the signature from the token's header and payload (using the secret for HS256, or the public key for RS256/ES256) and confirm it matches the signature attached to the token. If it doesn't, the token is forged or corrupted — reject it immediately and run no further checks. If it matches, the payload is now proven authentic, and the remaining steps can rely on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Check &lt;code&gt;exp&lt;/code&gt; (not expired).&lt;/strong&gt; Read the expiration timestamp and confirm the current time is before it. An expired token is rejected even though its signature is flawless. This is the check that makes a stolen token stop working on its own — and the entire reason short expiry times are worth setting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Check &lt;code&gt;nbf&lt;/code&gt; (not used before valid).&lt;/strong&gt; If the token carries a "not before" claim, confirm the current time is past it. A token presented before its activation moment is not yet valid and must be refused. In practice this rarely fires, but a complete validator checks it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Check &lt;code&gt;iss&lt;/code&gt; (expected issuer).&lt;/strong&gt; Confirm the &lt;code&gt;iss&lt;/code&gt; claim names an authentication server you actually trust. A token can be perfectly signed by &lt;em&gt;some&lt;/em&gt; issuer, but if it isn't &lt;em&gt;your&lt;/em&gt; issuer, it's irrelevant — reject it. This stops your service from honouring tokens minted by an unrelated, untrusted source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — Check &lt;code&gt;aud&lt;/code&gt; (intended audience).&lt;/strong&gt; Confirm the &lt;code&gt;aud&lt;/code&gt; claim names &lt;em&gt;your&lt;/em&gt; service. This is the check which is most often skipped — and skipping it is a genuine vulnerability. Without it, a token issued for the low-privilege analytics service would be accepted by the high-privilege billing service, simply because both trust the same issuer. The &lt;code&gt;aud&lt;/code&gt; check is what keeps a token usable &lt;em&gt;only&lt;/em&gt; where it was meant to be used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6 — Check &lt;code&gt;jti&lt;/code&gt; against a revocation list (if needed).&lt;/strong&gt; For most stateless setups this step is deliberately omitted — checking a token against a list means consulting a database, which sacrifices the very statelessness JWT was chosen for. But when you genuinely need the ability to cancel an individual token before it expires, this is where it happens: look up the token's &lt;code&gt;jti&lt;/code&gt; and reject it if it appears on a blocklist. This step is the bridge revocation problem, and the reason it's marked "if needed" is itself the subject of that section.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this looks like in code
&lt;/h3&gt;

&lt;p&gt;Previously we said: build a token by hand once to understand it, then use a library in production. Validation is precisely where that advice earns its keep. A good library performs the entire checklist for you — &lt;em&gt;if&lt;/em&gt; you tell it what to expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;  &lt;span class="c1"&gt;# the PyJWT library
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_jwt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_audience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HS256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;          &lt;span class="c1"&gt;# Step 1: which algorithm(s) to trust
&lt;/span&gt;            &lt;span class="n"&gt;audience&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;expected_audience&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# Step 5: the aud claim must match this
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Steps 2 and 3 — exp and nbf — are checked automatically by decode().
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExpiredSignatureError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;AuthError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Token has expired&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# Step 2 failed
&lt;/span&gt;    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvalidAudienceError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;AuthError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid audience&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# Step 5 failed
&lt;/span&gt;    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvalidSignatureError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;AuthError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Signature verification failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Step 1 failed
&lt;/span&gt;    &lt;span class="c1"&gt;# ... and so on for the other failure cases
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three details in that small function carry the section's whole lesson.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;&lt;code&gt;jwt.decode()&lt;/code&gt; does several checks at once.&lt;/strong&gt; A single call verifies the signature, the expiry, and the "not before" time. The library runs the checklist — but only the parts you've configured.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;a check you don't ask for doesn't happen.&lt;/strong&gt; Notice &lt;code&gt;audience=expected_audience&lt;/code&gt; is passed explicitly. Leave that argument out, and many libraries simply &lt;em&gt;skip the audience check entirely&lt;/em&gt; — Step 5 silently vanishes, and no error is ever raised. The token validates "successfully," and a real vulnerability sits quietly in your code. The same applies to the issuer. Safe validation means actively telling the library what to expect; its defaults are not your security policy.&lt;/p&gt;

&lt;p&gt;Third — and this is the most important line in the whole snippet — &lt;strong&gt;&lt;code&gt;algorithms=["HS256"]&lt;/code&gt; is not optional.&lt;/strong&gt; It tells the library exactly which signing algorithm(s) to accept. Omitting it, or filling it in carelessly, opens the single most infamous JWT vulnerability of all. We've now mentioned it twice as a forward pointer; later we finally show the attack itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  The takeaway
&lt;/h3&gt;

&lt;p&gt;A valid signature is the &lt;em&gt;first&lt;/em&gt; line of a six-line checklist, not the whole of it. Full validation confirms a token is &lt;strong&gt;authentic&lt;/strong&gt;(signature), &lt;strong&gt;current&lt;/strong&gt; (&lt;code&gt;exp&lt;/code&gt;, &lt;code&gt;nbf&lt;/code&gt;), &lt;strong&gt;from a source you trust&lt;/strong&gt; (&lt;code&gt;iss&lt;/code&gt;), &lt;strong&gt;meant for you&lt;/strong&gt; (&lt;code&gt;aud&lt;/code&gt;), and &lt;strong&gt;not revoked&lt;/strong&gt; (&lt;code&gt;jti&lt;/code&gt;, when required). A production-grade library will run every one of those checks — but only the ones you explicitly configure. The unsafe defaults are silent, and silence, in security, is exactly the danger.&lt;/p&gt;

&lt;p&gt;That sixth step — revocation — has been flagged twice now as something deferred. It's deferred because it isn't a simple checklist item; it's a genuine tension at the heart of the whole JWT design. The next section confronts it directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stateless Advantage — and the Revocation Problem
&lt;/h2&gt;

&lt;p&gt;This is the most important section in the article, and the most honest. Everything so far has built toward a single payoff — and that same payoff comes bundled with a single, genuine flaw. A clear-eyed engineer needs both halves. This section gives you both.&lt;/p&gt;

&lt;h3&gt;
  
  
  The win: a server that needs no memory
&lt;/h3&gt;

&lt;p&gt;Return to the very first idea of this series. An API key is a meaningless ticket; to learn what it permits, the server must make a &lt;strong&gt;database roundtrip&lt;/strong&gt; to the back room. A JWT prints the details on the ticket itself and stamps them so they can't be forged.&lt;/p&gt;

&lt;p&gt;Now we can state precisely what that buys you. When a JWT arrives, the receiving server checks it using &lt;strong&gt;only the token and a verification key the server already holds&lt;/strong&gt; — the shared secret for HS256, or the public key for RS256/ES256. The signature confirms the token is authentic; the claims inside answer &lt;em&gt;who the user is&lt;/em&gt; and &lt;em&gt;what they may do&lt;/em&gt;. Nothing else is consulted.&lt;/p&gt;

&lt;p&gt;That property is &lt;strong&gt;statelessness&lt;/strong&gt;: the server keeps no per-user, per-session memory. It needs no shared session store, no identity database lookup on the request path. And the benefits compound:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed.&lt;/strong&gt; No network trip to a database before real work begins. Verification is local arithmetic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale.&lt;/strong&gt; Add a hundred new servers and not one of them needs a connection to a session store. Each verifies tokens entirely on its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resilience.&lt;/strong&gt; Recall the microservices picture from figure 2 — a dozen services each verifying tokens. With JWT, none of them shares a session-store bottleneck. If that store would have existed, it would have been a single point of failure every service depended on. JWT removes it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the killer feature. It is the reason JWT became the default token of the distributed-systems era.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem: you can't un-issue a token
&lt;/h3&gt;

&lt;p&gt;Now the honest half.&lt;/p&gt;

&lt;p&gt;The very thing that makes a JWT powerful — it is &lt;strong&gt;self-contained&lt;/strong&gt;, valid purely on its own evidence — is the thing that makes it dangerous. A stateless server's logic is simply: &lt;em&gt;"the signature is good and the token hasn't expired, therefore I trust it."&lt;/em&gt; That logic has no step that asks anyone's permission. It never phones home.&lt;/p&gt;

&lt;p&gt;So consider: a token is &lt;strong&gt;stolen&lt;/strong&gt;. An attacker copies a user's valid, unexpired JWT — through a compromised device, an intercepted request, a leaked log file.&lt;/p&gt;

&lt;p&gt;With an old-style server-side session, the fix is instant. You delete the session record from the server. The next request carrying that session is a stranger; access is gone forever.&lt;/p&gt;

&lt;p&gt;With a JWT, there is &lt;strong&gt;no record to delete.&lt;/strong&gt; The token's authority lives &lt;em&gt;inside the token&lt;/em&gt;, in the attacker's possession. Every server that sees it performs the same local check, gets the same answer — &lt;em&gt;signature good, not expired&lt;/em&gt; — and grants access. The token keeps working, in the attacker's hands, &lt;strong&gt;until its &lt;code&gt;exp&lt;/code&gt; timestamp passes.&lt;/strong&gt; And nothing you do can hurry that moment along.&lt;/p&gt;

&lt;p&gt;State this plainly, because it is the crux of the entire JWT trade-off:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A JWT is &lt;strong&gt;self-contained by design&lt;/strong&gt;, which means it is &lt;strong&gt;non-revocable by design.&lt;/strong&gt; The server gave up its memory in exchange for speed — and a server with no memory has no way to remember that one particular token should now be refused.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not a bug to be patched. It is the direct, unavoidable shadow of the feature. Statelessness and instant revocation are two ends of the same stick: you cannot pick up one end without the other coming with it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Living with the trade-off
&lt;/h3&gt;

&lt;p&gt;You can't eliminate the problem, but you can &lt;em&gt;manage&lt;/em&gt; it. Three patterns are used in practice, and they sit on a deliberate spectrum — from "fully stateless, accept some risk" to "give back some statelessness, regain control."&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Short expiry times
&lt;/h4&gt;

&lt;p&gt;The simplest response: if a stolen token is valid until &lt;code&gt;exp&lt;/code&gt;, then &lt;strong&gt;make &lt;code&gt;exp&lt;/code&gt; soon.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set tokens to expire in fifteen minutes rather than twenty-four hours. A stolen token is still unstoppable — but only for a short window. The damage is time-boxed. This keeps the server fully stateless; you simply accept a small, bounded exposure instead of trying to abolish it.&lt;/p&gt;

&lt;p&gt;The objection is obvious, and it's a usability one: a token that expires every fifteen minutes would log the user out every fifteen minutes. Forcing someone to re-enter their password that often is unworkable. Which is exactly why short expiry never travels alone — it travels with the next pattern.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. The access token + refresh token pattern
&lt;/h4&gt;

&lt;p&gt;This is the pattern most production systems actually implement, and it resolves the usability objection cleanly by splitting one token into &lt;strong&gt;two tokens with two different jobs.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The access token&lt;/strong&gt; is a JWT, short-lived (say, 15 minutes). It rides along with every API request and does the real authorization work. Being a JWT, it is verified statelessly — fast, local, no database. If stolen, it's dangerous for only 15 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The refresh token&lt;/strong&gt; is long-lived (days or weeks), but it is &lt;strong&gt;not&lt;/strong&gt; used for ordinary API calls. Its &lt;em&gt;only&lt;/em&gt; power is to ask the authentication server for a fresh access token. It is sent rarely, to one endpoint only, and it is typically a stored, &lt;strong&gt;revocable&lt;/strong&gt; credential — the auth server &lt;em&gt;does&lt;/em&gt; keep a record of it.&lt;/li&gt;
&lt;/ul&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%2F6sl391xcshyvqjd7fuir.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%2F6sl391xcshyvqjd7fuir.png" alt="Figure 11. Access &amp;amp; Refresh Token Split Pattern" width="799" height="383"&gt;&lt;/a&gt; Figure 11. Access &amp;amp; Refresh Token Split Pattern&lt;/p&gt;

&lt;p&gt;The whole lifecycle is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. User logs in.
   → Auth server issues:  a short-lived ACCESS token  +  a long-lived REFRESH token.

2. For each API request:
   → Client sends the ACCESS token.  Service verifies it statelessly. Fast.

3. After ~15 minutes:
   → The ACCESS token expires. The next API call is rejected.

4. Client quietly sends the REFRESH token to the auth server.
   → Auth server checks the refresh token is still valid (and not revoked),
     then issues a brand-new short-lived ACCESS token.

5. Loop back to step 2. The user notices nothing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The elegance is in the division of labour. The token used &lt;em&gt;constantly&lt;/em&gt; (the access token) is stateless and fast, exactly where speed matters — but barely dangerous, because it expires almost immediately. The token that is &lt;em&gt;long-lived and genuinely dangerous&lt;/em&gt; (the refresh token) is used &lt;em&gt;rarely&lt;/em&gt;, hits only one endpoint, and &lt;strong&gt;can be revoked at that one chokepoint.&lt;/strong&gt; To cut off a compromised user, you invalidate their refresh token: within fifteen minutes their access token expires, the refresh fails, and they're locked out.&lt;/p&gt;

&lt;p&gt;You haven't achieved truly instant revocation — there's still that ≤15-minute tail — but you've shrunk the unstoppable window to something tolerable while keeping the request path stateless. For most systems, that is the right balance.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. A revocation blocklist
&lt;/h4&gt;

&lt;p&gt;When even a fifteen-minute window is unacceptable — banking, healthcare, anything where a compromised session must die &lt;em&gt;now&lt;/em&gt; — you need true immediate revocation. And here you must pay the honest price.&lt;/p&gt;

&lt;p&gt;The pattern: recall the &lt;strong&gt;&lt;code&gt;jti&lt;/code&gt;&lt;/strong&gt; claim, the token's unique serial number. To revoke a token immediately, add its &lt;code&gt;jti&lt;/code&gt; to a &lt;strong&gt;blocklist&lt;/strong&gt; — a list of "tokens that must now be refused" — and have every service check incoming tokens against it. To keep that check fast, the list lives in a high-speed in-memory store such as &lt;strong&gt;Redis&lt;/strong&gt;, not a slow disk database.&lt;/p&gt;

&lt;p&gt;Be clear-eyed about what this costs. Checking a blocklist means &lt;strong&gt;consulting a shared store on every request&lt;/strong&gt; — which is precisely the database roundtrip that JWT was chosen to eliminate. You have, deliberately, &lt;strong&gt;given back the stateless advantage.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But the surrender is partial, and that nuance matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A blocklist holds only &lt;em&gt;revoked&lt;/em&gt; tokens — a tiny set — not &lt;em&gt;every&lt;/em&gt; session. The store stays small.&lt;/li&gt;
&lt;li&gt;An in-memory store like Redis is far faster than the relational-database lookup the original API-key model implied.&lt;/li&gt;
&lt;li&gt;Entries can be evicted the moment a token's natural &lt;code&gt;exp&lt;/code&gt; passes — a revoked token already rejected by the expiry check needs no blocklist entry. The list stays small on its own.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this isn't a wholesale return to stateful sessions. It's a precise, conscious concession: trade a little speed, on a small fast lookup, to buy back the immediate-revocation capability — but only when the application's risk profile genuinely demands it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The honest summary
&lt;/h3&gt;

&lt;p&gt;JWT's defining feature and JWT's defining flaw are &lt;strong&gt;the same fact&lt;/strong&gt;, seen from two sides. A self-contained token lets a server decide &lt;em&gt;on its own&lt;/em&gt; — fast, scalable, memoryless. A self-contained token also cannot be &lt;em&gt;un-decided&lt;/em&gt; — once issued, it is authoritative until it expires.&lt;/p&gt;

&lt;p&gt;There is no pattern that makes that flaw vanish. There are only positions on a spectrum:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Statelessness&lt;/th&gt;
&lt;th&gt;Revocation speed&lt;/th&gt;
&lt;th&gt;Typical use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Short expiry only&lt;/td&gt;
&lt;td&gt;Fully stateless&lt;/td&gt;
&lt;td&gt;None — wait for &lt;code&gt;exp&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Low-risk, simple systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access + refresh tokens&lt;/td&gt;
&lt;td&gt;Stateless request path&lt;/td&gt;
&lt;td&gt;Bounded — within the access token's lifetime&lt;/td&gt;
&lt;td&gt;The mainstream default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;jti&lt;/code&gt; blocklist&lt;/td&gt;
&lt;td&gt;Partly stateful again&lt;/td&gt;
&lt;td&gt;Immediate&lt;/td&gt;
&lt;td&gt;High-stakes systems only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The engineering question is never "how do I make JWT revocable?" — it can't be made so without giving something up. The question is: &lt;strong&gt;how much immediate-revocation power does this system genuinely need, and how much statelessness am I willing to trade for it?&lt;/strong&gt; Answer that honestly, pick your point on the spectrum, and you are using JWT well.&lt;/p&gt;

&lt;p&gt;We've now covered what a JWT is, what it carries, how it's signed, how it's validated, and its central trade-off. One thing remains: the specific ways JWT implementations get attacked — including the single most infamous JWT vulnerability of all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;A JWT is a security mechanism, which makes its own failure modes worth studying with care. Most JWT disasters are not failures of the cryptography — the math is sound. They are failures of &lt;em&gt;implementation&lt;/em&gt;: a check skipped, a default trusted, a token stored carelessly. This section covers the four that matter most, beginning with the most famous of them all.&lt;/p&gt;

&lt;h3&gt;
  
  
  The algorithm confusion attack: &lt;code&gt;alg: none&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;We have pointed at this vulnerability twice and promised to show it here. It is the most infamous JWT bug ever, and what makes it memorable is that it turns the token's own honesty mechanism &lt;em&gt;against itself&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Recall the structure of the token. The &lt;strong&gt;header&lt;/strong&gt; declares which algorithm was used to sign the token, in its &lt;code&gt;alg&lt;/code&gt; field — &lt;code&gt;"alg": "HS256"&lt;/code&gt;. The &lt;strong&gt;signature&lt;/strong&gt; is the stamp that proves the token wasn't altered.&lt;/p&gt;

&lt;p&gt;Now recall a detail almost no one expects: the JWT standard defines a &lt;em&gt;legitimate&lt;/em&gt; algorithm value called &lt;strong&gt;&lt;code&gt;none&lt;/code&gt;&lt;/strong&gt;. It means "this token is unsigned." It exists for narrow cases where a token's integrity is already guaranteed by some other layer. It also creates a trapdoor.&lt;/p&gt;

&lt;p&gt;Here is the attack, step by step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. The attacker takes a real, valid token and decodes it
   (remember - the payload is NOT secret, anyone can read it).

2. The attacker EDITS the payload freely:
        "sub": "regular-user"   →   "sub": "admin"

3. The attacker changes the header:
        "alg": "HS256"          →   "alg": "none"

4. The attacker DELETES the signature entirely,
   leaving a token that ends in a final dot and nothing after it:
        header.payload.

5. The token is sent to the server.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now everything depends on one line of the server's code. A &lt;strong&gt;naively written verifier&lt;/strong&gt; reads the header, sees &lt;code&gt;"alg": "none"&lt;/code&gt;, and reasons: &lt;em&gt;"The header says this token is unsigned, so there is no signature to check. No signature to check means nothing fails. Token accepted."&lt;/em&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%2Fj667g6roi388lf9chprf.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%2Fj667g6roi388lf9chprf.png" alt="Figure 12. JWT Attack with " width="800" height="382"&gt;&lt;/a&gt; Figure 12. JWT Attack with "alg" set to "none"&lt;/p&gt;

&lt;p&gt;The forged token sails through. The attacker is now &lt;code&gt;admin&lt;/code&gt;. They never needed the secret key, never broke any cryptography — they simply &lt;strong&gt;let the token tell the server how to verify it&lt;/strong&gt;, and the token, under attacker control, said "don't bother."&lt;/p&gt;

&lt;p&gt;That is the heart of the trap: a naive verifier trusts the &lt;em&gt;token's own header&lt;/em&gt; to decide how the token should be checked. But the header is attacker-controlled. You have let the thing being inspected dictate the terms of its own inspection. (If this sounds like the embedded-public-key problem discussed earlier — a token vouching for its own trust — it is exactly the same mistake, in a different costume.)&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;The fix is one line, and you have already seen it:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HS256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;algorithms=["HS256"]&lt;/code&gt; argument is the entire defense. It instructs the library: &lt;em&gt;"I will accept HS256 and nothing else. I don't care what the token's header claims."&lt;/em&gt; A token arriving with &lt;code&gt;alg: none&lt;/code&gt; is now rejected before any verification logic runs, because &lt;code&gt;none&lt;/code&gt; is not on the list the &lt;em&gt;server&lt;/em&gt; chose.&lt;/p&gt;

&lt;p&gt;The principle, stated generally:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Never let the token decide how it should be verified.&lt;/strong&gt; The set of acceptable algorithms is a decision the server makes in advance and enforces — not a value it reads out of the untrusted header.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Modern, well-maintained libraries now refuse &lt;code&gt;alg: none&lt;/code&gt; by default and require you to specify allowed algorithms — which is exactly why we insisted on using a real library rather than hand-rolling verification. But the lesson outlives this one bug: a verifier must hold its own policy, never inherit it from the token.&lt;/p&gt;

&lt;h3&gt;
  
  
  A second face of algorithm confusion: HS256 vs RS256
&lt;/h3&gt;

&lt;p&gt;There is a subtler cousin of the same attack.&lt;/p&gt;

&lt;p&gt;Recall the asymmetric model: with RS256, the server signs with a &lt;strong&gt;private key&lt;/strong&gt; and publishes the &lt;strong&gt;public key&lt;/strong&gt; for anyone to verify with. The public key is, by design, &lt;em&gt;not secret&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Now picture a server configured for RS256 but with a careless verifier that accepts whatever algorithm the header names. An attacker takes the &lt;strong&gt;public key&lt;/strong&gt; — which is freely available — and uses it as if it were an &lt;strong&gt;HS256 shared secret&lt;/strong&gt;. They forge a token, set the header to &lt;code&gt;"alg": "HS256"&lt;/code&gt;, and sign it using the public key as the HMAC secret.&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%2Fa54js759hu26oilae780.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%2Fa54js759hu26oilae780.png" alt="Figure 13. RS256/HS256 Key Confusion Attack Path" width="800" height="385"&gt;&lt;/a&gt; Figure 13. RS256/HS256 Key Confusion Attack Path&lt;/p&gt;

&lt;p&gt;When the token arrives, the confused server sees &lt;code&gt;alg: HS256&lt;/code&gt;, fetches its RS256 public key as the "secret," runs the HS256 check — and it passes. The attacker has forged a valid token using only public information.&lt;/p&gt;

&lt;p&gt;The fix is the same fix: pin the accepted algorithm. A server expecting RS256 must specify &lt;code&gt;algorithms=["RS256"]&lt;/code&gt; and refuse everything else, so a token claiming &lt;code&gt;HS256&lt;/code&gt; is rejected outright. Once again — the server decides, not the token.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secret strength for HS256
&lt;/h3&gt;

&lt;p&gt;This one is simple and easily neglected. With HS256, the security of every token rests entirely on one shared secret. The signature is only as strong as that secret is hard to guess.&lt;/p&gt;

&lt;p&gt;A secret like &lt;code&gt;"secret"&lt;/code&gt;, &lt;code&gt;"password123"&lt;/code&gt;, or your application's name is not a secret — it is an invitation. An attacker who suspects HS256 can run an &lt;strong&gt;offline brute-force attack&lt;/strong&gt;: take any real token they've captured, and rapidly try millions of candidate secrets, checking each by recomputing the signature. A weak secret falls in seconds. Once it's found, the attacker can mint unlimited valid tokens for anyone.&lt;/p&gt;

&lt;p&gt;The defense is unglamorous but absolute:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a &lt;strong&gt;long, high-entropy, randomly generated&lt;/strong&gt; secret — at least 256 bits (32 random bytes), produced by a cryptographic random generator, never typed by a human.&lt;/li&gt;
&lt;li&gt;Store it as a &lt;strong&gt;secret&lt;/strong&gt; — in a secrets manager or environment configuration, never hard-coded in source, never committed to version control.&lt;/li&gt;
&lt;li&gt;This concern is HS256-specific. With RS256/ES256 there is no shared secret to guess; the private key is a different kind of object, guarded differently.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Token storage in the browser
&lt;/h3&gt;

&lt;p&gt;When a JWT is used to authenticate a user in a web browser, a practical question arises: &lt;em&gt;where does the browser keep the token between requests?&lt;/em&gt; This debate is mostly relevant to browser-based apps rather than the pure API-to-API use that is this series' main focus, so we'll keep it brief — but it's worth knowing the shape of it.&lt;/p&gt;

&lt;p&gt;There are two common choices, each with a different weakness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;localStorage&lt;/code&gt;&lt;/strong&gt; is a simple in-browser storage area. It's easy to use, but any JavaScript running on the page can read it. If an attacker manages to inject a malicious script into your site — a &lt;strong&gt;cross-site scripting (XSS)&lt;/strong&gt; attack — that script can read the token straight out of &lt;code&gt;localStorage&lt;/code&gt; and steal it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;An &lt;code&gt;httpOnly&lt;/code&gt; cookie&lt;/strong&gt; is a cookie the browser marks as off-limits to JavaScript. Even a successful XSS attack cannot read it directly, which closes the theft route above. The trade-off: cookies are sent by the browser automatically, which can expose the app to a different attack class — &lt;strong&gt;cross-site request forgery (CSRF)&lt;/strong&gt; — that needs its own separate defenses.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no universally "correct" answer; each option trades one risk for another. The genuinely important point sits underneath both:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A JWT is a &lt;strong&gt;bearer token&lt;/strong&gt; — like cash, whoever holds it can spend it. The token itself contains no proof that the &lt;em&gt;right&lt;/em&gt; person is presenting it. So the security of any browser-based JWT system depends heavily on simply &lt;em&gt;not letting the token get stolen&lt;/em&gt; — and that depends on the surrounding application being free of injection flaws.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Short expiry as a discipline
&lt;/h3&gt;

&lt;p&gt;The final consideration is not a new attack but a habit that blunts all of them — and we've already met it.&lt;/p&gt;

&lt;p&gt;Every threat in this section ends the same way: an attacker obtains a token they shouldn't have. And we already know JWT's hard truth — a stolen token cannot be recalled; it is valid until its &lt;code&gt;exp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This makes the expiry time itself a security control. A long-lived token turns any single theft into a long-lived breach. A short-lived one — minutes, not days — means a stolen token is a &lt;em&gt;briefly&lt;/em&gt; useful prize, paired with the refresh-token pattern so users never feel the churn.&lt;/p&gt;

&lt;p&gt;Short expiry doesn't &lt;em&gt;prevent&lt;/em&gt; theft. It &lt;strong&gt;caps the cost&lt;/strong&gt; of theft. It is the safety net beneath every other mistake, and it should be treated as non-negotiable discipline rather than a tuning knob.&lt;/p&gt;

&lt;h3&gt;
  
  
  The thread connecting all four
&lt;/h3&gt;

&lt;p&gt;Step back and the four considerations rhyme. The algorithm confusion attack is &lt;em&gt;trusting the token to define its own verification&lt;/em&gt;. Weak HS256 secrets are &lt;em&gt;trusting an easily guessed value to be hard&lt;/em&gt;. Careless token storage is &lt;em&gt;trusting a hostile environment to keep a bearer token safe&lt;/em&gt;. And the antidote running through all of them is the same instinct: &lt;strong&gt;trust must be anchored in decisions the server makes and controls — never in something the token, the attacker, or the environment supplies.&lt;/strong&gt; The cryptography in JWT is rarely what fails. The trust assumptions around it are.&lt;/p&gt;

&lt;p&gt;With the failure modes mapped, only the practical verdict remains: when JWT is the right tool for the job — and when it isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use JWT — and When Not To
&lt;/h2&gt;

&lt;p&gt;We've taken JWT apart completely: its structure, its claims, its signatures, its validation, its central trade-off, and its failure modes. Knowing how a tool works is not the same as knowing when to reach for it. This section is the practical verdict — a decision guide for an engineer building a real system.&lt;/p&gt;

&lt;p&gt;The honest framing is this: JWT is not a &lt;em&gt;better&lt;/em&gt; credential than the API key from Part II. It is a &lt;em&gt;different&lt;/em&gt; credential, built for a different job. Picking the right one means matching the tool to the situation.&lt;/p&gt;

&lt;h3&gt;
  
  
  When JWT is the right choice
&lt;/h3&gt;

&lt;p&gt;JWT earns its place whenever its defining feature — a &lt;strong&gt;self-contained, statelessly verifiable token&lt;/strong&gt; — solves a problem you actually have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stateless microservices — the killer use case.&lt;/strong&gt; This is the scenario JWT was built for, and the one where it has no real rival. When an application is split into many small services, and a request may pass through several of them, a JWT lets &lt;em&gt;each&lt;/em&gt; service verify the caller independently — with only a public key, no shared session store, no database roundtrip. The token carries identity and permissions with it. If there is one situation where JWT is unambiguously the answer, this is it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-domain authentication — SPAs and mobile apps.&lt;/strong&gt; Recall that JWT emerged partly because session cookies are bound to a single domain and fit modern clients poorly. A JWT is just a string. It travels comfortably from a single-page app or a native mobile application to an API hosted on an entirely different domain, with none of the domain-binding friction cookies impose. When your client and your API don't share an origin, JWT removes a real obstacle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Short-lived service-to-service tokens.&lt;/strong&gt; When one internal service needs to call another and prove "this request is genuinely from me," a short-lived JWT is an excellent fit. Its built-in expiry means these machine-to-machine tokens clean up after themselves — no long-lived credential left lying around, no revocation process to run. They are minted, used, and expire, all within minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;As a session-scoped layer on top of a long-term API key.&lt;/strong&gt; This is the subtlest fit, and it shows JWT and the API key working &lt;em&gt;together&lt;/em&gt; rather than competing. An API key (Part II) is excellent for stable, long-term &lt;em&gt;identity&lt;/em&gt; — "this is the Acme Corp account." A JWT is excellent for short-lived, fine-grained &lt;em&gt;authorization&lt;/em&gt; — "this particular request, in this 15-minute window, may write to invoices." A common production pattern: a client authenticates &lt;em&gt;once&lt;/em&gt; with its long-lived API key, and in exchange receives a series of short-lived JWTs that carry the actual per-request permissions. The API key answers &lt;em&gt;who you are over time&lt;/em&gt;; the JWT answers &lt;em&gt;what you may do right now&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to reach for something else
&lt;/h3&gt;

&lt;p&gt;JWT's weaknesses are not hidden and we stated them plainly. Two situations expose them directly, and in both, a different tool is the honest choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you need immediate, reliable revocation.&lt;/strong&gt; This is JWT's defining flaw, and it is worth refusing to paper over. A standard JWT &lt;em&gt;cannot be un-issued&lt;/em&gt; — it is valid until &lt;code&gt;exp&lt;/code&gt;, full stop. If your system has a hard requirement that a compromised credential must die &lt;em&gt;the instant&lt;/em&gt; you say so — with no fifteen-minute tail, no acceptable window — then a self-contained token is fighting your requirement.&lt;/p&gt;

&lt;p&gt;The alternative here is an &lt;strong&gt;opaque token&lt;/strong&gt; checked by &lt;strong&gt;introspection&lt;/strong&gt;: the token is a meaningless reference string (like the API key of Part II — a coat-check ticket), and the receiving service asks a central authority, on every request, "is this token still valid?" That &lt;em&gt;is&lt;/em&gt; a database roundtrip. You are deliberately giving up statelessness — but in exchange you get exactly the instant, authoritative revocation that statelessness cost you. For high-stakes systems, that is the right trade.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When the token is genuinely long-lived.&lt;/strong&gt; If a credential is meant to last for months — a stable identifier for a server, a long-running integration — then JWT's machinery is working against you. Its central feature, the self-contained expiring claim set, brings no benefit when nothing is meant to expire soon, and its central flaw, non-revocability, becomes a serious liability over a long lifespan. For durable, long-term identity, a plain &lt;strong&gt;API key&lt;/strong&gt; is simpler, and — because it's checked against a database anyway — it can be revoked the moment you need to. Don't reach for a JWT's complexity to do an API key's job.&lt;/p&gt;

&lt;h3&gt;
  
  
  The decision in one table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your situation&lt;/th&gt;
&lt;th&gt;Reach for&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Many services must verify a caller independently&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;JWT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stateless verification, no shared session store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client and API live on different domains&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;JWT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A plain string travels anywhere; no cookie domain-binding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Short-lived, self-expiring service-to-service calls&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;JWT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in expiry; no revocation process needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stable, long-term identity for an account or server&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;API key&lt;/strong&gt; (Part II)&lt;/td&gt;
&lt;td&gt;Simpler; revocable; nothing needs to expire soon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A compromised credential must be killable &lt;em&gt;instantly&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Opaque token + introspection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;True immediate revocation, at the cost of statelessness&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The underlying judgment
&lt;/h3&gt;

&lt;p&gt;Reduce all of the above to a single question and it becomes easy to remember:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Does this system need a server to decide &lt;em&gt;on its own&lt;/em&gt;, fast and at scale — or does it need a central authority to stay &lt;em&gt;in control&lt;/em&gt;, able to revoke instantly?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;JWT is the tool for the first. It trades central control for local, stateless speed — a brilliant trade when speed and scale are what you need, a poor one when control is. Opaque tokens and API keys are the tools for the second.&lt;/p&gt;

&lt;p&gt;There is no "best" credential, only a credential that fits. An engineer who can name &lt;em&gt;why&lt;/em&gt; they chose JWT — and name what they gave up to get it — is using it well. One who reaches for it by default, because it is the modern-sounding option, has skipped the only question that matters.&lt;/p&gt;

&lt;p&gt;That leaves one piece of this series unfinished. JWT answers, beautifully, the question &lt;em&gt;who are you?&lt;/em&gt; It does not answer a different and harder one: &lt;em&gt;how do you let a third party act on your behalf — without ever handing them your password?&lt;/em&gt; That is the problem the final part was built to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Three Tools, Three Jobs
&lt;/h2&gt;

&lt;p&gt;We began this article with a coat-check ticket and we end it with a boarding pass. That shift — from a meaningless token the server must look up, to a self-describing token the server can read and trust on sight — is the whole of what JWT contributes. It's worth gathering the journey into a single picture before we close.&lt;/p&gt;

&lt;h3&gt;
  
  
  What JWT actually gave us
&lt;/h3&gt;

&lt;p&gt;Strip away the Base64URL, the claims tables, the algorithm names, and one idea remains: &lt;strong&gt;a JWT moves metadata out of the database and into the token itself, stamped so it cannot be forged.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That single move is the source of everything else in this article. It is why a server can be &lt;strong&gt;stateless&lt;/strong&gt; — verifying a request with only a key it already holds, no roundtrip, no shared session store. It is why JWT scales so gracefully across microservices and travels so freely across domains. And it is — unavoidably — why a JWT cannot be un-issued: a server that gave up its memory for speed has no memory in which to record that one token should now be refused.&lt;/p&gt;

&lt;p&gt;The strength and the flaw are the same fact seen from two sides. An engineer who holds both halves in mind at once understands JWT. One who remembers only the strength will, sooner or later, be surprised by the flaw.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three methods, side by side
&lt;/h3&gt;

&lt;p&gt;This series has now equipped us with three distinct credentials. They are not a ranking — not worse, better, best. They are three tools for three different jobs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;Basic Auth&lt;/strong&gt; (Part I)&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;API Key&lt;/strong&gt; (Part II)&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;JWT&lt;/strong&gt; (Part III)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What it is&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Username + password sent on every request&lt;/td&gt;
&lt;td&gt;A single opaque secret string&lt;/td&gt;
&lt;td&gt;A signed, self-describing token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Carries its own data?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No — it's just a reference&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes&lt;/strong&gt; — identity, permissions, expiry, all inside&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Server needs a lookup?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes, every request&lt;/td&gt;
&lt;td&gt;Yes, every request&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No&lt;/strong&gt; — verified statelessly with a key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Built-in expiry?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes&lt;/strong&gt; — the &lt;code&gt;exp&lt;/code&gt; claim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Revocable immediately?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (change the password)&lt;/td&gt;
&lt;td&gt;Yes (delete the key)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No&lt;/strong&gt; — valid until it expires&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best at&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simple, internal, low-stakes access&lt;/td&gt;
&lt;td&gt;Stable, long-term identity&lt;/td&gt;
&lt;td&gt;Stateless, distributed, short-lived authorization&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the table top to bottom and the trade-off snaps into focus. Basic Auth and the API key keep their information in the database — which makes them slower per request, but instantly revocable. JWT carries its information in the token — which makes it fast and stateless, but stubbornly non-revocable. &lt;strong&gt;You cannot have both.&lt;/strong&gt; Every authentication design is, at heart, a choice about where the trust lives: in a central store the server must consult, or in the token the server can read on its own.&lt;/p&gt;

&lt;p&gt;Choosing well means naming that trade-off out loud — not reaching for the most modern-sounding option by reflex, but asking what &lt;em&gt;this&lt;/em&gt; system actually needs, and what it can afford to give up.&lt;/p&gt;

&lt;h3&gt;
  
  
  The one question that remains
&lt;/h3&gt;

&lt;p&gt;And yet, for all three of these methods, notice what they have in common. Basic Auth, the API key, and the JWT all answer the same single question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Who are you?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They are mechanisms of &lt;em&gt;authentication&lt;/em&gt; — proving identity. Each one assumes the party presenting the credential is the party it belongs to, acting on its own behalf.&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%2Ffya2cr2sssedz2ls8yvq.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%2Ffya2cr2sssedz2ls8yvq.png" alt="Figure 14. Authentication vs. Authorization" width="800" height="456"&gt;&lt;/a&gt; Figure 14. Authentication vs. Authorization&lt;/p&gt;

&lt;p&gt;But a great deal of the modern web does not work that way. When you let a photo-printing service reach into your cloud storage, or allow an analytics dashboard to read your calendar, something more delicate is happening. You are granting &lt;em&gt;a third party&lt;/em&gt; access to &lt;em&gt;your&lt;/em&gt; resources — and you would never dream of handing them your password to do it.&lt;/p&gt;

&lt;p&gt;None of the three tools in this series solves that. A JWT can prove &lt;em&gt;who you are&lt;/em&gt; beautifully; it has nothing to say about how you safely &lt;em&gt;delegate&lt;/em&gt; a sliver of your access to someone else.&lt;/p&gt;

&lt;p&gt;That problem — &lt;strong&gt;how to grant a third party limited access to your resources without ever sharing your credentials&lt;/strong&gt; — is precisely the problem the next and final part of this series was built around. It is the problem of &lt;em&gt;authorization&lt;/em&gt; and &lt;em&gt;delegation&lt;/em&gt;, and the answer is the framework that quietly underpins nearly every "Sign in with…" button and connected app on the internet: &lt;strong&gt;OAuth 2.0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We have spent three articles learning, in ever-greater depth, how to answer &lt;em&gt;who are you?&lt;/em&gt; It is time to ask the harder question.&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>API Authentication: Part II. API Keys</title>
      <dc:creator>Eugene Zimin</dc:creator>
      <pubDate>Mon, 02 Dec 2024 08:38:33 +0000</pubDate>
      <link>https://dev.to/eugene-zimin/api-authentication-part-ii-api-keys-2l51</link>
      <guid>https://dev.to/eugene-zimin/api-authentication-part-ii-api-keys-2l51</guid>
      <description>&lt;h2&gt;
  
  
  Few Words of History
&lt;/h2&gt;

&lt;p&gt;Following our exploration of &lt;a href="https://dev.to/eugene-zimin/practical-guide-to-api-authentication-part-i-basic-authentication-4ndn"&gt;Basic Authentication&lt;/a&gt;, the next in line are API Keys - a widely adopted authentication mechanism that addresses many of the limitations of Basic Authentication while maintaining simplicity in implementation. API Keys represent an evolution in API authentication, offering a balance between security and usability that has made them a popular choice for modern web services.&lt;/p&gt;

&lt;p&gt;The concept of API Keys emerged as web services began to scale beyond simple and single client-server interactions. Unlike Basic Authentication's username-password paradigm, API Keys introduced a single, long-lived token approach that better suited programmatic access to APIs. This shift reflected the growing need for machine-to-machine communication in distributed systems, where traditional username-password combinations proved cumbersome.&lt;/p&gt;

&lt;p&gt;The rise of public APIs in the mid-2000s catalyzed the widespread adoption of API Keys. Services like Google Maps, Twitter, and Amazon Web Services popularized this authentication method, demonstrating its effectiveness in managing access to public APIs at scale. This adoption marked a significant step forward in API security, introducing concepts like rate limiting, usage tracking, and granular access control that weren't easily achievable with Basic Authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding API Keys
&lt;/h2&gt;

&lt;p&gt;At their core, API Keys are long, unique strings that serve as both identifier and authenticator for API clients. Unlike Basic Authentication's two-part credential system, an API Key combines identification and authentication into a single token. This architectural choice fundamentally changes how systems handle authentication and brings several important implications for system design.&lt;/p&gt;

&lt;p&gt;Consider a practical example of how this unified approach manifests in real-world applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Traditional Basic Auth approach
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;authenticate_with_basic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;stored_password_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_password_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;stored_password_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# Username doesn't exist
&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;verify_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stored_password_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# API Key approach
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;authenticate_with_api_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;key_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_key_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;key_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# Invalid key
&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;key_data&lt;/span&gt;  &lt;span class="c1"&gt;# Contains identity and permissions
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the basic authentication process requires two distinct steps: first finding the user, then verifying their password. The API Key approach consolidates these steps - the key itself contains or points to all necessary information. When using Basic Authentication, the system must maintain separate storage for usernames and password hashes, while API Keys allow for a more simplified data structure.&lt;/p&gt;

&lt;p&gt;Let's examine a more complex example that demonstrates the practical implications of this unified approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;APIKeyAuthenticator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;database_connection&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;database_connection&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;issue_key_for_service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;permission_level&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Generate a cryptographically secure random key
&lt;/span&gt;        &lt;span class="n"&gt;raw_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token_urlsafe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Create a structured key with embedded metadata
&lt;/span&gt;        &lt;span class="n"&gt;key_prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_generate_prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key_prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;raw_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="c1"&gt;# Store key metadata
&lt;/span&gt;        &lt;span class="n"&gt;key_metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;service&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;permissions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;permission_level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;last_used&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store_key_metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key_metadata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key_metadata&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requested_action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;key_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_key_metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;key_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="c1"&gt;# Update usage timestamp
&lt;/span&gt;        &lt;span class="n"&gt;key_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;last_used&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_key_metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_check_permissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;permissions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;requested_action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This implementation demonstrates several key concepts. The &lt;code&gt;issue_key_for_service&lt;/code&gt; method creates a structured API key that embeds useful metadata directly in the key format. The key consists of three parts: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a service-specific prefix, &lt;/li&gt;
&lt;li&gt;a timestamp, and &lt;/li&gt;
&lt;li&gt;a random component. 
This structure allows for quick identification of the key's origin and age without accessing the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;validate_request&lt;/code&gt; method shows how authentication and authorization become intertwined with API Keys. A single database lookup retrieves all necessary information about the key's associated service and permissions. This contrasts with Basic Authentication, where separate lookups might be needed for authentication and permission checking.&lt;/p&gt;

&lt;p&gt;The practical implications of this unified approach become clear when examining system behavior. For instance, when a service needs to rotate its API key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rotate_service_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authenticator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old_key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Verify the old key is valid
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;authenticator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rotate_key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="c1"&gt;# Get existing permissions from old key
&lt;/span&gt;    &lt;span class="n"&gt;old_key_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authenticator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_key_metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;permission_level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_key_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;permissions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Issue new key with same permissions
&lt;/span&gt;    &lt;span class="n"&gt;new_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authenticator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;issue_key_for_service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;permission_level&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Invalidate old key after grace period
&lt;/span&gt;    &lt;span class="n"&gt;authenticator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule_key_deletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;grace_period_hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;new_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rotation process demonstrates the elegance of the unified token approach. The entire service identity and permission set transfers seamlessly to the new key, while the old key can be gracefully deprecated. In a Basic Authentication system, changing credentials might require updating multiple database records and managing password change workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Management and Storage
&lt;/h3&gt;

&lt;p&gt;The management and storage of API keys present unique challenges that require careful consideration. In a production environment, the storage mechanism must balance security, performance, and scalability. Let's explore a comprehensive implementation that addresses these concerns with the class called &lt;code&gt;APIKeyManader&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;APIKeyManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;database_connection&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;database_connection&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hash_algorithm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sha256&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key_prefix_length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Hashing
&lt;/h4&gt;

&lt;p&gt;Our class will contain few instance variables defining DB connection, hashing algorithm and the prefix length. Then we need to consider a method to create a hash key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hash_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hash_algorithm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage of the method is pretty simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk_test_abc123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating a hash instead of storing an API Key directly in the database is another security implication. If an attacker gains read access to this database through SQL injection, backup file exposure, or any other security breach, they immediately obtain valid API keys for all clients. These keys remain fully functional until manually revoked.&lt;/p&gt;

&lt;p&gt;Here's what happens when an attacker breaches this database:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;They see only hashes, which are one-way mathematical transformations&lt;/li&gt;
&lt;li&gt;Even with modern computing power, it's practically impossible to reverse these hashes to obtain the original API keys&lt;/li&gt;
&lt;li&gt;Each key uses a unique salt, preventing attackers from using rainbow tables (pre-computed hash databases)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example of what an attacker might see in the database
&lt;/span&gt;&lt;span class="n"&gt;compromised_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;record_1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key_hash&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;salt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a1b2c3d4e5f6g7h8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_123&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;record_2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key_hash&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;472b07b9fcf2c2451e8781e944bf5f77cd8457488d8f45f2318eb1b5b4175276&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;salt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;9i8h7g6f5e4d3c2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_456&lt;/span&gt;&lt;span class="sh"&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;it won't be helpful as it is practically impossible to restore the API Key based on the hash information only. The security of this approach relies on some mathematical principles.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hash functions are one-way operations&lt;/li&gt;
&lt;li&gt;The same input always produces the same hash&lt;/li&gt;
&lt;li&gt;Small changes in input produce completely different hashes&lt;/li&gt;
&lt;li&gt;It's computationally infeasible to find an input that produces a specific hash&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Demonstration of hash properties
&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk_live_abc123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;hash1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hash of &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hash1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Changing just one character produces a completely different hash
&lt;/span&gt;&lt;span class="n"&gt;api_key_modified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk_live_abc124&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;hash2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key_modified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hash of &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_key_modified&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hash2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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 will output two completely different hashes, even though the input strings differ by only one character:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Hash of &lt;span class="s1"&gt;'pk_live_abc123'&lt;/span&gt;: 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
Hash of &lt;span class="s1"&gt;'pk_live_abc124'&lt;/span&gt;: 472b07b9fcf2c2451e8781e944bf5f77cd8457488d8f45f2318eb1b5b4175276
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Adding Metadata
&lt;/h4&gt;

&lt;p&gt;However creating an abstract key is almost meaningless as we need to associate this key with specific user information. For this purpose we need to be able to link the API Key to the use data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_key_with_metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                               &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                               &lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                               &lt;span class="n"&gt;expiry_days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Generate random bytes and encode as base64
&lt;/span&gt;        &lt;span class="n"&gt;random_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;random_part&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random_bytes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# Create structured API key with prefix
&lt;/span&gt;        &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk_live&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_production&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk_test&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;random_part&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="c1"&gt;# Calculate expiration date
&lt;/span&gt;        &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;expiry_days&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Prepare metadata for storage
&lt;/span&gt;        &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;permissions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;expires_at&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key_hash&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;raw_key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;create_key_with_metadata&lt;/code&gt; method represents a fundamental operation in API key management - the creation of a new API key for a client.&lt;/p&gt;

&lt;p&gt;The method accepts three parameters: a client identifier (who the key is for), a list of permissions (what they can do), and an optional expiry period in days (when the key will stop working). By default, keys expire after 90 days to enforce regular rotation.&lt;/p&gt;

&lt;p&gt;The output consists of two critical components packaged together: the API key itself and its associated metadata. The API key is what we'll provide to the client - it's their credential for accessing our API. The metadata, on the other hand, contains everything our system needs to know about this key: who owns it, what they're allowed to do with it, when it was created, when it expires, and a secure hash of the key for verification purposes.&lt;/p&gt;

&lt;p&gt;Think of this like issuing a security badge at a facility. The physical badge (analogous to our API key) goes to the person, while all the information about that badge - who it belongs to, what doors it can open, when it expires - gets stored in the security system's database (our metadata).&lt;/p&gt;

&lt;p&gt;The key itself follows a specific format: a prefix indicating whether it's for production or testing, followed by a cryptographically secure random string. This structured format helps with key management and debugging, while maintaining the security properties we need.&lt;/p&gt;

&lt;p&gt;The method ensures several security properties: unpredictability through cryptographic randomness, safe storage through hashing, and automatic expiration through timestamp tracking. These elements combine to create a robust, manageable authentication token system.&lt;/p&gt;

&lt;h4&gt;
  
  
  Validating API Key
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;key_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_key_metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

        &lt;span class="c1"&gt;# Check if key has expired
&lt;/span&gt;        &lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;expires_at&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+00:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

        &lt;span class="c1"&gt;# Update last used timestamp
&lt;/span&gt;        &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;last_used&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_key_metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;validate_key&lt;/code&gt; method serves as the gatekeeper for our API authentication system. When a client makes a request to our API with their key, this method determines whether that key is valid and active.&lt;/p&gt;

&lt;p&gt;The process begins with the client's provided API key. The method first calculates its hash - the same process we used when storing the key. This hash acts as our lookup identifier in the database. Remember, we never store the actual API keys, only their hashes.&lt;/p&gt;

&lt;p&gt;If we can't find any metadata associated with this hash in our database, it means the key either never existed or has been revoked. In this case, we immediately return &lt;code&gt;None&lt;/code&gt;, indicating an invalid key.&lt;/p&gt;

&lt;p&gt;For keys that do exist, we perform a temporal validation. The stored metadata includes an expiration timestamp - we parse this timestamp (converting it from ISO format with UTC indicator to a Python &lt;code&gt;datetime&lt;/code&gt; object) and compare it with the current time. If the key has expired, we again return &lt;code&gt;None&lt;/code&gt;. This expiration check is vital for enforcing security through key rotation.&lt;/p&gt;

&lt;p&gt;When a key passes both these checks (exists and hasn't expired), we perform one final housekeeping task: updating the &lt;code&gt;last_used&lt;/code&gt; timestamp. This tracking helps with key management, letting us identify unused keys that might need to be revoked.&lt;/p&gt;

&lt;p&gt;Finally, for valid keys, we return the complete metadata associated with that key. This metadata contains essential information like the client's identity and permissions, which our API can use to make authorization decisions.&lt;/p&gt;

&lt;p&gt;Think of this process like checking an ID card at a secure facility:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We first verify the ID exists in our system&lt;/li&gt;
&lt;li&gt;We check if it hasn't expired&lt;/li&gt;
&lt;li&gt;We log when it was last used&lt;/li&gt;
&lt;li&gt;If everything checks out, we retrieve all the information about what the ID holder is allowed to do&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This method is called frequently - potentially for every API request - so it needs to be efficient while maintaining strict security standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Patterns
&lt;/h2&gt;

&lt;p&gt;When implementing API key authentication, there are few different transmission methods of API Keys between client and server. We'll examine the main approaches and their implications for real-world applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Header-based Authentication
&lt;/h3&gt;

&lt;p&gt;The HTTP header approach represents the industry standard for API key transmission. By placing the API key in a custom header, we benefit from several security advantages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_api_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;  &lt;span class="c1"&gt;# Always verify SSL certificates
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and here is the usage example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk_8a4c6f3e_1a2b3c4d5e6f7g8h9i0j&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_api_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.example.com/data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The header-based approach offers several advantages. HTTP headers are not typically logged by default in most web servers and proxy systems and they're not directly visible by user. This means our API keys won't appear in log files, reducing the risk of accidental exposure. Additionally, headers aren't cached by browsers or included in browser history, providing another layer of security.&lt;/p&gt;

&lt;p&gt;When implementing header-based authentication, we commonly prefix our custom header with &lt;code&gt;X-&lt;/code&gt; to indicate it's a non-standard header. The &lt;code&gt;X-API-Key&lt;/code&gt; header has become a de facto standard in the industry, though some systems use variations like &lt;code&gt;Authorization&lt;/code&gt; with a custom prefix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query Parameter Authentication
&lt;/h3&gt;

&lt;p&gt;Query parameter authentication places the API key directly in the URL as a parameter. Here's how this manifests in practice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;https://api.example.com/v1/data?api_key=pk_test_abc123def456
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach faces several security challenges:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Server Logs Exposure: Most web servers include the complete URL in their logs:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`192.168.1.1 - - [01/Dec/2024:10:00:00 +0000] "GET /v1/data?api_key=pk_test_abc123def456 HTTP/1.1" 200 2326`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Browser History: The key becomes part of browsing history:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Browser&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;url&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;https://api.example.com/v1/data?api_key=pk_test_abc123def456&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;timestamp&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;2024-12-01T10:00:00&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;title&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;API Request&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;ol&gt;
&lt;li&gt;Referrer Headers: When clicking links on the API response page, the API key might be sent as a referrer:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Referer: https://api.example.com/v1/data?api_key=pk_test_abc123def456
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While not recommended for production systems, query parameter authentication remains in use for some specific scenarios. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Static HTML/JavaScript environments where header modification isn't possible:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://api.example.com/v1/image?api_key=pk_test_abc123"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Direct browser access for development and testing:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://api.example.com/v1/test?api_key=pk_test_abc123"&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Legacy system compatibility where header modification isn't supported.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;When implementing API key authentication, security considerations extend far beyond the mere generation and validation of keys.&lt;/p&gt;

&lt;p&gt;The fundamental challenge lies in the nature of API keys themselves - they are long-lived credentials that grant access to potentially sensitive resources. Unlike session-based authentication systems where credentials expire relatively quickly, API keys often persist for extended periods, making them attractive targets for malicious actors. This persistence, while convenient for legitimate users, requires us to implement robust security measures throughout the key lifecycle.&lt;/p&gt;

&lt;p&gt;Consider an API key like a physical key to a building. Just as a physical key requires secure storage, controlled distribution, and periodic replacement, API keys demand similar careful management. The loss or compromise of an API key can have far-reaching consequences, potentially exposing not just individual resources but entire systems to unauthorized access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Rotation and Revocation
&lt;/h3&gt;

&lt;p&gt;Key rotation and revocation form the cornerstone of long-term API security strategy. While API keys might seem permanent, treating them as such introduces significant security risks. Let's examine how we implement these crucial security practices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;APIKeyRotationManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotation_period&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grace_period&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rotation period represents our security lifecycle. Setting it to 90 days balances security needs with operational convenience. The grace period allows systems to transition smoothly between old and new keys without service interruption.&lt;/p&gt;

&lt;p&gt;Think of this like replacing locks in a building - we need to ensure new keys are distributed before old ones stop working. Here's how we manage this process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;should_rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key_metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;key_age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;key_metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+00:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Standard age-based rotation
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key_age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotation_period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="c1"&gt;# Check for suspicious activity patterns
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_detect_unusual_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key_metadata&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rotate_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old_key_metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="c1"&gt;# Creates a new key while maintaining the old one during grace period.
&lt;/span&gt;    &lt;span class="c1"&gt;# Generate new key with same permissions
&lt;/span&gt;    &lt;span class="n"&gt;new_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_generate_key&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;new_metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;old_key_metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;permissions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;old_key_metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;permissions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rotated_from&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;old_key_metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key_hash&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Update old key metadata
&lt;/span&gt;    &lt;span class="n"&gt;old_key_metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rotating&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;old_key_metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rotates_at&lt;/span&gt;&lt;span class="sh"&gt;'&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="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grace_period&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;new_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_metadata&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our rotation strategy includes several critical patterns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Regular rotation based on key age&lt;/li&gt;
&lt;li&gt;Forced rotation when suspicious activity is detected&lt;/li&gt;
&lt;li&gt;Overlapping validity periods to prevent service disruption&lt;/li&gt;
&lt;li&gt;Maintenance of key lineage for audit purposes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sometimes, we need more immediate action. Key revocation provides this capability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;revoke_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key_metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# this is a soft revoke
&lt;/span&gt;    &lt;span class="n"&gt;revocation_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;revoked&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;revoked_at&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;revocation_reason&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;last_known_use&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key_metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;last_used&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Update key metadata with revocation information
&lt;/span&gt;    &lt;span class="n"&gt;key_metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;revocation_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Trigger any necessary security alerts
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;suspicious_activity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;security_breach&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_trigger_security_alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key_metadata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also implement a validation system that respects both rotation and revocation states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_key_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key_metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Validates a key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s status considering rotation and revocation.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key_metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;revoked&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key_metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rotating&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;rotation_deadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;key_metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rotates_at&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+00:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rotation_deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach to key lifecycle management ensures our system maintains security while remaining operationally viable. Regular rotation prevents the risks associated with permanent credentials, while our revocation system provides immediate response capability for security incidents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rate Limiting and Usage Tracking
&lt;/h3&gt;

&lt;p&gt;Rate limiting and usage tracking serve as essential defensive mechanisms in API authentication systems. Just as a security guard monitors the frequency of entries into a building, these systems protect our API from abuse while providing valuable insights into usage patterns.&lt;/p&gt;

&lt;p&gt;Let's examine a comprehensive implementation that handles both concerns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RateLimiter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requests_per_second&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requests_per_minute&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requests_per_hour&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requests_per_day&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;second_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;minute_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hour_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;day_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;windows&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;second&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;minute&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hour&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;day&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&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;This implementation introduces a multi-window rate limiting approach. Think of it like a building's security system that tracks entries across different time scales - from immediate access control to daily visitor limits. Here's how we enforce these limits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_rate_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;limits_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

        &lt;span class="c1"&gt;# Reset counters if time windows have elapsed
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;windows&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;second&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;second_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;windows&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;second&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;windows&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;minute&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;minute_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;windows&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;minute&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;

        &lt;span class="c1"&gt;# Update counters
&lt;/span&gt;        &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;second_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;minute_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

        &lt;span class="c1"&gt;# Check against limits
&lt;/span&gt;        &lt;span class="n"&gt;limits_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;second&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;second_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requests_per_second&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;minute&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;minute_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requests_per_minute&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;# Record usage pattern for analysis
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_record_usage_pattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limits_status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="n"&gt;limits_status&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alongside rate limiting, we implement comprehensive usage tracking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UsageTracker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage_patterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;usage_record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;endpoint&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;endpoint&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;response_status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;response_time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;response_time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_ip&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_ip&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage_patterns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usage_record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analyze_usage_pattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;patterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage_patterns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;patterns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;risk_level&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;recent_patterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;patterns&lt;/span&gt; 
                          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_is_recent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;

        &lt;span class="c1"&gt;# these methods are separate part of implementation:
&lt;/span&gt;        &lt;span class="c1"&gt;# self._calculate_risk_level
&lt;/span&gt;        &lt;span class="c1"&gt;# self._detect_anomalies
&lt;/span&gt;        &lt;span class="c1"&gt;# self._summarize_usage
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;risk_level&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_calculate_risk_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recent_patterns&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unusual_activity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_detect_anomalies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recent_patterns&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;usage_summary&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_summarize_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recent_patterns&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;These systems work together to provide several critical security functions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prevention of abuse through multi-window rate limiting&lt;/li&gt;
&lt;li&gt;Detection of unusual usage patterns that might indicate compromised keys&lt;/li&gt;
&lt;li&gt;Collection of usage metrics for billing and capacity planning&lt;/li&gt;
&lt;li&gt;Early warning system for potential security incidents&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally we may create adaptive rate limiting based on observed patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;adjust_limits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usage_analysis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# get data fromt the previous method
&lt;/span&gt;    &lt;span class="n"&gt;usage_analysis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analyze_usage_pattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;risk_level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;usage_analysis&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;risk_level&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;normal_usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;usage_analysis&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;usage_summary&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;average_daily_requests&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;risk_level&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Implement stricter limits for high-risk usage patterns
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requests_per_minute&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requests_per_minute&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normal_usage&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 50% above normal
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;risk_level&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;low&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Gradually relax limits for trusted keys
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requests_per_minute&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requests_per_minute&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;  &lt;span class="c1"&gt;# 10% increase
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach to rate limiting and usage tracking provides both immediate protection against abuse and long-term insights into API usage patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use API Keys
&lt;/h2&gt;

&lt;p&gt;The decision to implement API key authentication should be based on a careful analysis of system requirements, security needs, and usage patterns. Let's examine the primary scenarios where API keys excel as an authentication mechanism.&lt;/p&gt;

&lt;h3&gt;
  
  
  Public APIs with Moderate Security Requirements
&lt;/h3&gt;

&lt;p&gt;In the context of public APIs, such as weather data services or public reference databases, API keys serve as an ideal authentication method. For instance, consider a public mapping service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_location_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://maps.example.com/v1/location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lng&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;coordinates&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="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach works well because it balances accessibility with basic security and usage tracking. The data, while valuable, doesn't contain sensitive information that would require more robust authentication methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Developer-Focused Services
&lt;/h3&gt;

&lt;p&gt;When building services primarily used by developers, API keys provide a straightforward integration path. Consider a code analysis service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CodeAnalysisAPI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://analysis.example.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analyze_repository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repo_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/analyze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;repository&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;repo_url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Developers appreciate this approach because it requires minimal setup and integrates easily with common development tools and workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Services Requiring Usage Tracking
&lt;/h3&gt;

&lt;p&gt;When business requirements demand precise usage monitoring, API keys excel. Consider a content delivery network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContentDelivery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage_tracker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UsageTrackingSystem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;serve_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Validate key and record usage
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage_tracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check_quota&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Quota exceeded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Track bandwidth consumption
&lt;/span&gt;        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage_tracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record_bandwidth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This system allows for precise tracking of resource consumption, enabling accurate billing and capacity planning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internal Microservices
&lt;/h3&gt;

&lt;p&gt;Within secure networks, API keys provide an efficient service-to-service authentication mechanism. Consider a microservice architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InternalServiceClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service_name&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;request_internal_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;X-Service-Name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://internal-service/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this context, API keys provide a lightweight yet effective way to maintain service boundaries and track inter-service communications.&lt;/p&gt;

&lt;p&gt;However, there are scenarios where API keys might not be the best choice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;High-security environments requiring user-specific actions&lt;/li&gt;
&lt;li&gt;Systems handling sensitive personal data&lt;/li&gt;
&lt;li&gt;Financial services requiring transaction-level authentication&lt;/li&gt;
&lt;li&gt;Applications needing fine-grained user permissions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In these cases, more robust authentication methods like OAuth 2.0 or JWT-based systems might be more appropriate. The key is to match the authentication method to the specific security and operational requirements of your system.&lt;/p&gt;

&lt;p&gt;This comprehensive understanding of when to use API keys helps ensure we implement the right authentication mechanism for our specific use case, balancing security needs with operational efficiency.&lt;/p&gt;

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

&lt;p&gt;API Keys represent a significant evolution in API authentication, offering a balance of security and simplicity that makes them ideal for many modern use cases. While they may not provide the robust security features of OAuth 2.0 or JWT tokens, their straightforward implementation and management make them an excellent choice for many applications.&lt;/p&gt;

&lt;p&gt;The key to successful API Key implementation lies in proper key management, secure transmission, and comprehensive monitoring. By following the best practices outlined in this guide and implementing appropriate security measures, developers can leverage API Keys to build secure and scalable API authentication systems.&lt;/p&gt;

&lt;p&gt;As we continue our journey through API authentication methods, API Keys serve as a bridge between the simplicity of Basic Authentication and the complexity of token-based systems like OAuth 2.0, which we'll explore in our next article in this series.&lt;/p&gt;

</description>
      <category>api</category>
      <category>cloud</category>
      <category>security</category>
      <category>development</category>
    </item>
    <item>
      <title>API Authentication: Part I. Basic Authentication</title>
      <dc:creator>Eugene Zimin</dc:creator>
      <pubDate>Mon, 02 Dec 2024 04:10:15 +0000</pubDate>
      <link>https://dev.to/eugene-zimin/practical-guide-to-api-authentication-part-i-basic-authentication-4ndn</link>
      <guid>https://dev.to/eugene-zimin/practical-guide-to-api-authentication-part-i-basic-authentication-4ndn</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;API authentication serves as the fundamental gatekeeper in the inter service communication, establishing a mechanism for identifying and verifying the clients attempting to interact with our systems. At its core, authentication acts as a digital handshake between client and server, ensuring that only legitimate requests gain access to protected resources.&lt;/p&gt;

&lt;p&gt;In distributed systems architecture, this process involves multiple layers of verification. When a client - whether a mobile application, web service, or another system - attempts to access an API endpoint, it must first prove its identity. This proof typically comes in the form of credentials, tokens, or cryptographic signatures. Much like a passport serves as a trusted form of identification in international travel, these digital credentials provide a way to verify the authenticity of incoming requests.&lt;/p&gt;

&lt;p&gt;The story of API authentication begins in the early days of web services, when the primary concern was simply identifying the caller, who often was a user. The initial mechanisms were straightforward - a simple username and password combination transmitted with each request. As web applications grew more complex and interconnected, these basic authentication methods proved insufficient. Client applications, third-party services, and automated systems began consuming APIs alongside human users, each requiring different security considerations.&lt;/p&gt;

&lt;p&gt;The rise of service-oriented architectures in the late 1990s and early 2000s introduced new challenges. APIs needed to handle machine-to-machine communication, delegate access rights, and manage varying levels of permissions - all while maintaining security and performance.&lt;/p&gt;

&lt;p&gt;The evolution reflected a fundamental shift in how systems interact with each other. No longer was it sufficient to simply verify a username and password - systems needed to establish trust, manage sessions, handle token expiration, and implement multiple layers of security. This transformation mirrors the broader evolution of the internet from a simple information-sharing platform to a complex ecosystem of interconnected services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication vs. Authorization
&lt;/h2&gt;

&lt;p&gt;Before we begin, we need to differentiate and understand the difference between authentication and authorization. In scientific terms, authentication and authorization represent distinct but complementary security processes that work together to create a comprehensive security model.&lt;/p&gt;

&lt;p&gt;Authentication answers the question: "Who is making this request?" Think of it like airport security screening. When passing through security, travelers must present valid identification (passport or ID) to prove they are who they claim to be. Similarly, API requests must present valid credentials to prove their identity. Just as a passport contains specific security features that can be validated, API credentials contain unique identifiers and cryptographic elements that verify their authenticity.&lt;/p&gt;

&lt;p&gt;Authorization, on the other hand, addresses the question: "What is this authenticated entity allowed to do?" Continuing with our airport analogy, once a traveler's identity is confirmed through their passport (authentication), their boarding pass determines which flight they can board and which areas of the airport they can access (authorization). A first-class ticket might grant access to exclusive lounges, while a standard ticket restricts access to general waiting areas.&lt;/p&gt;

&lt;p&gt;In the context of API security, this distinction becomes critical to keep in mind. Consider a banking system: when logging into a banking application, providing correct username and password authenticates the user (proves identity), but the bank's authorization system determines which accounts they can view or what types of transactions they can perform. A customer service representative might be authenticated into the system but authorized only to view customer information, while a financial advisor might be authorized to perform trades on behalf of clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Authentication: The Core
&lt;/h2&gt;

&lt;p&gt;Basic Authentication represents one of the foundational authentication methods in web services. Despite its simplicity, understanding its mechanics provides crucial insights into authentication principles. Born in the early days of web protocols, Basic Authentication introduces core concepts that persist in modern authentication systems.&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%2Fvy3n9ktmzmq610n4m2rf.jpeg" 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%2Fvy3n9ktmzmq610n4m2rf.jpeg" alt="Basic Auth Flow" width="800" height="480"&gt;&lt;/a&gt;&lt;br&gt;
Figure 1. Basic Authentication Flow&lt;/p&gt;

&lt;p&gt;At its heart, Basic Authentication operates on a straightforward premise - each request carries credentials in its header. These credentials consist of a &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; pair, combined and encoded using a base64 algorithm with the prefix word &lt;code&gt;Basic&lt;/code&gt; before encoded string. Below is the example how to encode the &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; into the base64 string using command line interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'username:password'&lt;/span&gt; | &lt;span class="nb"&gt;base64
&lt;/span&gt;&lt;span class="nv"&gt;dXNlcm5hbWU6cGFzc3dvcmQ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create a Basic Authentication token we need to get that string and simply add the word &lt;code&gt;Basic&lt;/code&gt; before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This is the client code, which may be another service or browser
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_basic_auth_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Combine username and password with a colon
&lt;/span&gt;    &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# Convert the string to bytes, encode in base64, and decode back to string
&lt;/span&gt;    &lt;span class="n"&gt;encoded_credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Create the authorization header
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Basic &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;encoded_credentials&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Example implementation
&lt;/span&gt;&lt;span class="n"&gt;auth_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_basic_auth_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;researcher&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secure_pwd123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Generated header: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;auth_header&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output example:
# Generated header: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generated Basic Authentication token in form of the string &lt;code&gt;Basic dXNlcm5hbWU6cGFzc3dvcmQ=&lt;/code&gt; should be injected by client application for every request made towards the secured API endpoint. When examining network traffic, such a header should appear in every request, made by client application (web browser or another service):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/api/resource&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api.example.com&lt;/span&gt;
&lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Basic dXNlcm5hbWU6cGFzc3dvcmQ=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server's role in this exchange involves reversing the encoding process and validating the credentials. This creates a simple yet complete authentication cycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This is server code, which validates credentials on the server
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_basic_auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth_header&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# check the authentication approach
&lt;/span&gt;        &lt;span class="n"&gt;auth_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auth_header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# return False if the approach is not Basic Auth
&lt;/span&gt;        &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Basic&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;auth_tokens&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="c1"&gt;# Decode from base64
&lt;/span&gt;        &lt;span class="n"&gt;decoded_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth_tokens&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="c1"&gt;# Convert to string and split credentials
&lt;/span&gt;        &lt;span class="n"&gt;decoded_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decoded_bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decoded_str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# In practice, compare against securely stored credentials in Database
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;verify_credentials_in_DB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each request follows this validation cycle, establishing a stateless authentication pattern. &lt;/p&gt;

&lt;h3&gt;
  
  
  Base64 Encoding
&lt;/h3&gt;

&lt;p&gt;When examining Basic Authentication's encoding mechanism, understanding why base64 is chosen over other encoding methods reveals fascinating insights into network protocol design. Let's take a look at it through both theoretical explanation and practical demonstrations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="c1"&gt;# Let's examine what happens with special characters in credentials
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;demonstrate_base64_necessity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Original credentials with special characters
&lt;/span&gt;    &lt;span class="n"&gt;raw_credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Original string: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;raw_credentials&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Show raw bytes representation
&lt;/span&gt;    &lt;span class="n"&gt;raw_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw_credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Raw bytes: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;raw_bytes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Show base64 encoded version
&lt;/span&gt;    &lt;span class="n"&gt;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_bytes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Base64 encoded: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;encoded&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;encoded&lt;/span&gt;

&lt;span class="c1"&gt;# Example with special characters
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;demonstrate_base64_necessity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;research@lab.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p@ssw:rd!123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Considering we provide the following data for input &lt;code&gt;research@lab.com&lt;/code&gt; as the username and &lt;code&gt;p@ssw:rd!123&lt;/code&gt; as the password, we should expect the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Original string: research@lab.com:p@ssw:rd!123&lt;/span&gt;
&lt;span class="c"&gt;# Raw bytes: b'research@lab.com:p@ssw:rd!123'&lt;/span&gt;
&lt;span class="c"&gt;# Base64 encoded: cmVzZWFyY2hAbGFiLmNvbTpwQHNzdzpyZCExMjM=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Base64 encoding serves several critical purposes in Basic Authentication.&lt;/p&gt;

&lt;p&gt;First, HTTP headers must comply with ASCII character restrictions. Consider credentials containing non-ASCII characters or special symbols that might interfere with HTTP header parsing. Base64 encoding solves this by converting any binary data into a subset of ASCII characters (&lt;code&gt;A-Z&lt;/code&gt;, &lt;code&gt;a-z&lt;/code&gt;, &lt;code&gt;0-9&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, and &lt;code&gt;=&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Let's demonstrate this with a more complex example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analyze_character_safety&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Original string might contain problematic characters
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Original: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Show problematic characters in HTTP headers
&lt;/span&gt;    &lt;span class="n"&gt;problematic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nf"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;126&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;problematic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Problematic characters: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;problematic&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Encode to base64
&lt;/span&gt;    &lt;span class="n"&gt;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Safe base64: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;encoded&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Verify all characters are HTTP-safe
&lt;/span&gt;    &lt;span class="n"&gt;safe_chars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nf"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;126&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;All characters HTTP-safe: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;safe_chars&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Example with various challenging characters
&lt;/span&gt;&lt;span class="nf"&gt;analyze_character_safety&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user:パスワード&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Japanese characters
&lt;/span&gt;&lt;span class="nf"&gt;analyze_character_safety&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user:password&lt;/span&gt;&lt;span class="se"&gt;\n\r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Control characters
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, using &lt;code&gt;user:パスワード"&lt;/code&gt; in first case and &lt;code&gt;user:password\n\r&lt;/code&gt; in the second case we would expect to get the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Original: user:パスワード&lt;/span&gt;
&lt;span class="c"&gt;# Problematic characters: ['パ', 'ス', 'ワ', 'ー', 'ド']&lt;/span&gt;
&lt;span class="c"&gt;# Safe base64: dXNlcjrjg5Hjgrnjg/rjg7zjg4k=&lt;/span&gt;
&lt;span class="c"&gt;# All characters HTTP-safe: True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another aspect is the colon character &lt;code&gt;:&lt;/code&gt; used as a delimiter between username and password. Without encoding, this would create ambiguity in parsing the credentials. Base64 encoding ensures the colon in the original credentials doesn't interfere with the username-password delimiter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;demonstrate_colon_handling&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# A username containing a colon would be problematic
&lt;/span&gt;    &lt;span class="n"&gt;problematic_username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;research:team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secure123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;problematic_username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ambiguous raw format: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;demonstrate_colon_handling&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without encoding, this would be ambiguous and we won't know which colon is the delimiter?:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ambiguous raw format: research:team:secure123&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To solve that we need to add the following code at the end of the &lt;code&gt;demonstrate_colon_handling&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# Base64 encoding resolves this ambiguity
&lt;/span&gt;    &lt;span class="n"&gt;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unambiguous encoded format: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;encoded&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Demonstrate successful decoding
&lt;/span&gt;    &lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Decoded back: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decoded&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the result now we should expect the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ambiguous raw format: research:team:secure123&lt;/span&gt;
&lt;span class="c"&gt;# Unambiguous encoded format: cmVzZWFyY2g6dGVhbTpzZWN1cmUxMjM=&lt;/span&gt;
&lt;span class="c"&gt;# Decoded back: research:team:secure123&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Base64 encoding also provides a minor level of obfuscation. While it's important to note that this is not encryption and offers no real security, it prevents casual observers from immediately reading credentials in network traces. &lt;/p&gt;

&lt;p&gt;It is clear that base64 encoding in Basic Authentication isn't about security but about ensuring reliable transport of credential information across HTTP protocols. It's a elegant solution to the challenge of sending potentially complex credential data within the constraints of HTTP headers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Considerations
&lt;/h3&gt;

&lt;p&gt;While Basic Authentication provides a straightforward implementation, its security profile requires careful consideration, as Transport Layer Security (TLS) becomes critical when using Basic Authentication. Consider the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlparse&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_basic_auth_credentials&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://api.example.com/data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;auth_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;construct_basic_auth_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;researcher&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Examining the request details
&lt;/span&gt;    &lt;span class="n"&gt;parsed_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;parsed_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheme&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Credentials will be transmitted in encoded but unencrypted form&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Raw auth header visible in transit: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;auth_header&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we run the following code with the HTTP schema (not HTTPS) we will see the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;# Unencrypted HTTP request (vulnerable)
&lt;/span&gt;&lt;span class="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/api/data&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api.example.com&lt;/span&gt;
&lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Basic cmVzZWFyY2hlcjpwYXNzd29yZDEyMw==&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and if we run it over HTTPS connection - we will get encrypted data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;# HTTPS encrypted request (secure)
[encrypted data stream]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTTP Secured connection is important here because it provides encryption on transport layer and prevents MITM attacks. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A man-in-the-middle attack occurs when a malicious actor positions themselves between the client and server during communication. Think of it like intercepting a postal mail - the attacker can read, modify, or even replace the contents before forwarding them to the intended recipient. In Basic Authentication, since credentials are merely encoded in base64 (not encrypted), an intercepted request reveals the username and password as clearly as reading an open letter.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What makes MITM particularly dangerous for Basic Authentication? Every request to the server includes these encoded credentials. This repeated transmission creates multiple opportunities for credential theft. Once intercepted, these credentials remain valid until explicitly changed, as Basic Authentication lacks built-in expiration or revocation mechanisms. The attacker can reuse the stolen credentials indefinitely, potentially accessing sensitive data or performing unauthorized operations.&lt;/p&gt;

&lt;p&gt;The situation changes dramatically when HTTPS enters the picture. HTTPS establishes a secure encrypted tunnel between client and server through Transport Layer Security (TLS). Continuing our postal analogy, HTTPS is like sending a letter in a secure diplomatic pouch that can only be opened by the intended recipient. Even if intercepted, the encrypted contents remain unreadable to the attacker.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use Basic Authentication
&lt;/h2&gt;

&lt;p&gt;Despite its limitations, Basic Authentication remains relevant in specific scenarios where its simplicity and ease of implementation outweigh its constraints. Understanding these use cases helps make informed decisions about authentication strategies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Development and Testing Environments
&lt;/h3&gt;

&lt;p&gt;During the development phase, teams often need quick, straightforward authentication mechanisms to test API functionality. Basic Authentication serves this purpose effectively, allowing developers to focus on core functionality without the complexity of more sophisticated authentication systems.&lt;/p&gt;

&lt;p&gt;In development environments, this approach facilitates rapid iteration and testing. However, it's important to ensure these simplified credentials never make their way into production systems. Development configurations should remain strictly separated from production environments and never stored in Git. When building and testing applications, secrets such as credentials, API keys, and tokens should be managed through environment variables or dedicated secrets management systems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Load environment variables from .env for development
&lt;/span&gt;        &lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basic_auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AUTH_USERNAME&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AUTH_PASSWORD&lt;/span&gt;&lt;span class="sh"&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;Development teams should maintain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;.env.example&lt;/code&gt; file in version control showing the required variables without actual values&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;.gitignore&lt;/code&gt; file that explicitly excludes &lt;code&gt;.env&lt;/code&gt; files and other secret-containing configurations&lt;/li&gt;
&lt;li&gt;Separate configuration management for each environment (development, staging, production)&lt;/li&gt;
&lt;li&gt;Documentation explaining how to securely obtain and configure credentials&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For local development, create a &lt;code&gt;.env&lt;/code&gt; file that's never committed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env&lt;/span&gt;
&lt;span class="nv"&gt;AUTH_USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev_user
&lt;span class="nv"&gt;AUTH_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev_password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Internal Network Communications
&lt;/h3&gt;

&lt;p&gt;Within controlled, internal networks, Basic Authentication can provide adequate security when combined with proper transport layer protection. This is particularly relevant for microservices architectures where services communicate within a trusted network boundary. In such environments, services often need to authenticate each other rapidly and efficiently which makes Basic Authentication is the preferable approach. The controlled nature of internal networks, combined with additional security measures like network segmentation, firewalls, and intrusion detection systems, creates multiple layers of protection. Organizations commonly implement Basic Authentication for service-to-service communication in scenarios where the network perimeter is well-defined and access is strictly controlled through VPNs or private networks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;internal_service_call&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;internal_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://internal-api.local/data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;internal_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;service_account&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;internal_token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;  &lt;span class="c1"&gt;# Still enforce SSL certificate verification
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Command-Line Tools and Scripts
&lt;/h3&gt;

&lt;p&gt;Basic Authentication proves valuable for command-line interfaces and automation scripts where simplicity and direct implementation are priorities. These tools often operate in controlled environments where the additional complexity of token-based authentication might be unnecessary. When building automation tools for internal use, developers need quick, reliable authentication methods that can be easily implemented across different programming languages and platforms. The simplicity of implementation allows teams to focus on core functionality while maintaining adequate security through proper credential management and secure communication channels.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring and Health Check Endpoints
&lt;/h3&gt;

&lt;p&gt;For simple monitoring endpoints that provide basic system health information, Basic Authentication offers a straightforward way to prevent unauthorized access while maintaining easy integration with monitoring tools. Monitoring systems require consistent, reliable access to health check endpoints while preventing unauthorized access to potentially sensitive system information. The stateless nature of Basic Authentication makes it particularly suitable for these endpoints, as monitoring tools can easily include credentials in their requests without managing complex token lifecycles or session states. System administrators can quickly configure monitoring tools with Basic Authentication credentials, enabling automated health checks and alerts while maintaining a basic security barrier against unauthorized access attempts. The simplicity of Basic Authentication also facilitates easy integration with various monitoring platforms and tools, making it a practical choice for health check endpoints in both development and production environments.&lt;/p&gt;

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

&lt;p&gt;Basic Authentication, despite its age and simplicity, continues to serve as a foundational element in API security architecture. The security considerations highlight the critical importance of implementing Basic Authentication exclusively over HTTPS connections. Without proper transport layer security, the base64 encoding offers no real protection against man-in-the-middle attacks, potentially exposing sensitive credentials to malicious actors. This vulnerability underscores why Basic Authentication should be deployed thoughtfully and in appropriate contexts.&lt;/p&gt;

&lt;p&gt;Basic Authentication remains valuable when matched with the right requirements and security context. In development environments, it facilitates rapid testing and iteration. Within internal networks, it enables efficient service-to-service communication. For command-line tools and monitoring systems, it provides a straightforward authentication mechanism that balances security with simplicity.&lt;/p&gt;

&lt;p&gt;However, it's important to recognize that Basic Authentication is just one tool in the modern API security toolkit. While it excels in specific scenarios, more complex applications often require more sophisticated authentication mechanisms like OAuth 2.0 or JWT tokens. The key to successful implementation lies in understanding these limitations and choosing Basic Authentication only when its simplicity aligns with the security requirements and operational context of your system.&lt;/p&gt;

&lt;p&gt;As we continue to build and secure modern APIs, Basic Authentication serves as a reminder that sometimes the simplest solution, when properly implemented and appropriately deployed, can be the right choice. Its longevity in the field of web security testifies to the enduring value of straightforward, well-understood security mechanisms in specific contexts.&lt;/p&gt;

</description>
      <category>development</category>
      <category>cloud</category>
      <category>api</category>
      <category>security</category>
    </item>
    <item>
      <title>Debugging SSH connections: A Comprehensive Guide</title>
      <dc:creator>Eugene Zimin</dc:creator>
      <pubDate>Tue, 26 Nov 2024 21:35:26 +0000</pubDate>
      <link>https://dev.to/eugene-zimin/debugging-ssh-connections-a-comprehensive-guide-1hoc</link>
      <guid>https://dev.to/eugene-zimin/debugging-ssh-connections-a-comprehensive-guide-1hoc</guid>
      <description>&lt;p&gt;SSH (Secure Shell) is the backbone of remote system administration and secure remote access, serving millions of developers and system administrators daily. However, when SSH connections fail, the cryptographic nature of the protocol can make debugging challenging. The complex interplay between authentication mechanisms, encryption algorithms, and network layers often obscures the root cause of connection issues. This complexity is further compounded by the protocol's security-first design, where error messages are intentionally vague to prevent potential attackers from gathering system information. Whether we're dealing with key authentication failures, network connectivity issues, or configuration mismatches, understanding the underlying SSH architecture becomes critical for effective troubleshooting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding SSH Connection Process
&lt;/h2&gt;

&lt;p&gt;Before diving into debugging, it's important to understand how SSH establishes connections. The process follows these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;TCP connection establishment (Port 22 by default)&lt;/li&gt;
&lt;li&gt;Protocol version exchange&lt;/li&gt;
&lt;li&gt;Key exchange and server authentication&lt;/li&gt;
&lt;li&gt;Client authentication&lt;/li&gt;
&lt;li&gt;Session establishment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Detailed process is highlighted in another article: &lt;a href="https://dev.to/eugene-zimin/configuring-ssh-to-access-remote-server-2ljk"&gt;Configuring SSH to Access Remote Server&lt;/a&gt; dedicated to overview this process in a deeper level.&lt;/p&gt;

&lt;p&gt;Each step can fail for different reasons, producing various error messages. Understanding these steps helps pinpoint where issues occur.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common SSH Errors and Solutions
&lt;/h2&gt;

&lt;p&gt;When working with SSH connections, we frequently encounter various error messages that might seem cryptic at first glance. While SSH's error reporting is intentionally terse for security reasons, each error message provides valuable clues about the underlying issue. Understanding these common errors and their potential solutions not only helps us resolve issues faster but also deepens our knowledge of SSH's security model and connection process. Many of these errors stem from incorrect configurations, permission issues, or network problems, and they often appear during system upgrades, after server migrations, or when setting up new environments. Let's explore the most frequent SSH connection issues and their systematic solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connection Refused: Understanding Server Accessibility
&lt;/h3&gt;

&lt;p&gt;One of the most common SSH errors we encounter during daily operations looks deceptively simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh: connect to host example.com port 22: Connection refused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error message indicates that our SSH server is completely unreachable, though the underlying cause requires careful investigation. Most frequently, we discover that the SSH daemon (&lt;code&gt;sshd&lt;/code&gt;) has stopped running on the target server. This typically happens after system updates, configuration changes, or occasional service crashes. A quick check of the service status often reveals the problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status sshd
● ssh.service - OpenBSD Secure Shell server
     Loaded: loaded &lt;span class="o"&gt;(&lt;/span&gt;/lib/systemd/system/ssh.service&lt;span class="p"&gt;;&lt;/span&gt; enabled&lt;span class="o"&gt;)&lt;/span&gt;
     Active: inactive &lt;span class="o"&gt;(&lt;/span&gt;dead&lt;span class="o"&gt;)&lt;/span&gt; since Mon 2024-01-15 09:23:45 UTC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pay attention that the server is shown as &lt;code&gt;inactive (dead)&lt;/code&gt; and usually it should be started with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start sshd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Network connectivity issues present another common culprit. Corporate or cloud provider firewalls might be blocking the default SSH port 22, requiring us to verify firewall rules. In cloud environments like AWS or GCP, this often means checking both the instance's security group and network ACLs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-L&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;ssh
&lt;span class="c"&gt;# No output indicates potential firewall blocking&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes the issue stems from DNS resolution problems or incorrect IP addressing. We can validate basic network connectivity using standard networking tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ping example.com
ping: example.com: Name or service not known

&lt;span class="nv"&gt;$ &lt;/span&gt;telnet example.com 22
telnet: Unable to connect to remote host: Connection refused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all these checks pass but the connection still fails, the server itself might be experiencing issues, possibly due to resource exhaustion or hardware problems. In such cases, server logs become our primary diagnostic tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/syslog
Jan 26 10:15:32 server kernel: Out of memory: Kill process 1234 &lt;span class="o"&gt;(&lt;/span&gt;sshd&lt;span class="o"&gt;)&lt;/span&gt; score 28 or sacrifice child
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Permission Denied
&lt;/h3&gt;

&lt;p&gt;One of the most frustrating SSH errors occurs during the authentication phase, presenting itself as a seemingly simple message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Permission denied &lt;span class="o"&gt;(&lt;/span&gt;publickey,password&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This deceptively brief message indicates an authentication failure, though its resolution often requires careful investigation. The error message's format actually provides our first clue - the terms "publickey" and "password" in parentheses tell us which authentication methods the server attempted before denying access.&lt;/p&gt;

&lt;p&gt;When troubleshooting this error, we often find that the username doesn't match the remote system's records. For example, while connecting to an Ubuntu server, we might mistakenly use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh admin@ubuntu-server
Permission denied &lt;span class="o"&gt;(&lt;/span&gt;publickey,password&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the correct username should be 'ubuntu':&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh ubuntu@ubuntu-server
Welcome to Ubuntu 22.04.2 LTS...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Private key mismatches represent another common scenario. The SSH server maintains a strict relationship between private and public key pairs, and even a slight mismatch will trigger this error. We can investigate key-related issues by enabling verbose output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh &lt;span class="nt"&gt;-v&lt;/span&gt; ubuntu@ubuntu-server
...
debug1: Trying private key: /home/user/.ssh/id_rsa
debug1: Trying private key: /home/user/.ssh/id_ed25519
debug1: No more authentication methods to try.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This verbose output shows SSH attempting to use each private key file it finds. The sequence shows that SSH couldn't successfully authenticate with any available key file. When you see 'No more authentication methods to try', it means SSH has exhausted all configured authentication methods (in this case, trying both RSA and ED25519 keys) without success.&lt;/p&gt;

&lt;p&gt;A particularly tricky variant occurs when all credentials appear correct, but file permissions are preventing SSH from accepting the keys. SSH enforces strict permission requirements for security reasons. We commonly see this when copying key files between systems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; ~/.ssh/id_ed25519
&lt;span class="nt"&gt;-rw-rw-r--&lt;/span&gt; 1 user user 464 Nov 26 10:15 /home/user/.ssh/id_ed25519

&lt;span class="nv"&gt;$ &lt;/span&gt;ssh ubuntu@ubuntu-server
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0664 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s1"&gt;'/home/user/.ssh/id_ed25519'&lt;/span&gt; are too open.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The solution requires setting appropriate permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.ssh/id_ed25519
&lt;span class="nv"&gt;$ &lt;/span&gt;ssh ubuntu@ubuntu-server
Welcome to Ubuntu 22.04.2 LTS...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For those cases where the server has our public key but still denies access, examining the server's auth log often reveals the underlying issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/auth.log
Nov 26 10:15:32 ubuntu-server sshd[12345]: Authentication refused: bad ownership or modes &lt;span class="k"&gt;for &lt;/span&gt;directory /home/ubuntu/.ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Host Key Verification Failed
&lt;/h3&gt;

&lt;p&gt;During routine SSH operations, we occasionally encounter an alarming message that stops us in our tracks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now &lt;span class="o"&gt;(&lt;/span&gt;man-in-the-middle attack&lt;span class="o"&gt;)!&lt;/span&gt;
It is also possible that a host key has just been changed.
The fingerprint &lt;span class="k"&gt;for &lt;/span&gt;the ED25519 key sent by the remote host is
SHA256:abcdef1234567890abcdef1234567890.
Please contact your system administrator.
Add correct host key &lt;span class="k"&gt;in&lt;/span&gt; /home/user/.ssh/known_hosts to get rid of this message.
Offending ECDSA key &lt;span class="k"&gt;in&lt;/span&gt; /home/user/.ssh/known_hosts:3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error, while alarming, serves as a critical security feature in SSH's trust model. When we first connect to a server, SSH stores its unique fingerprint in our &lt;code&gt;known_hosts&lt;/code&gt; file. Any subsequent changes to this fingerprint trigger this warning, protecting us from potential security breaches.&lt;/p&gt;

&lt;p&gt;In cloud environments, this error frequently occurs after server rebuilds. For instance, when working with AWS EC2 instances:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh ubuntu@ec2-12-34-56-78.compute-1.amazonaws.com
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The instance might have been terminated and recreated, generating a new host key. We can verify this through AWS console or CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;aws ec2 describe-instance-history &lt;span class="nt"&gt;--instance-id&lt;/span&gt; i-1234567890abcdef0
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"InstanceHistoryEvents"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"EventType"&lt;/span&gt;: &lt;span class="s2"&gt;"instanceStop"&lt;/span&gt;,
            &lt;span class="s2"&gt;"EventTime"&lt;/span&gt;: &lt;span class="s2"&gt;"2024-01-25T10:00:00Z"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For known legitimate changes, we can remove the old key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh-keygen &lt;span class="nt"&gt;-R&lt;/span&gt; ec2-12-34-56-78.compute-1.amazonaws.com
&lt;span class="c"&gt;# Host ec2-12-34-56-78.compute-1.amazonaws.com found: line 3&lt;/span&gt;
/home/user/.ssh/known_hosts updated.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, when this error occurs unexpectedly, particularly with production servers, it warrants immediate investigation. We can examine the server's SSH fingerprint directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh-keyscan &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nb"&gt;hostname&lt;/span&gt;
&lt;span class="c"&gt;# hostname:22 SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1&lt;/span&gt;
&lt;span class="nb"&gt;hostname &lt;/span&gt;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For added security, we can compare this with the fingerprint provided by our infrastructure provider or system administrator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh-keygen &lt;span class="nt"&gt;-lf&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;ssh-keyscan &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nb"&gt;hostname &lt;/span&gt;2&amp;gt;/dev/null&lt;span class="o"&gt;)&lt;/span&gt;
256 SHA256:abcdef1234567890abcdef1234567890 &lt;span class="nb"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ED25519&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In cases involving DNS changes, we might see this error when a domain name starts pointing to a different server. A quick DNS lookup can confirm such changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;dig +short &lt;span class="nb"&gt;hostname
&lt;/span&gt;93.184.216.34  &lt;span class="c"&gt;# Note if this IP has changed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Slow Connections
&lt;/h3&gt;

&lt;p&gt;While SSH generally provides very responsive connections, we occasionally encounter frustratingly slow sessions that impact us. These performance issues often manifest in various ways: delayed command responses, laggy terminal output, or prolonged initial connection times. &lt;/p&gt;

&lt;p&gt;One of the most common culprits involves DNS resolution delays. When establishing an SSH connection, the server attempts to resolve the client's hostname by default. In environments with misconfigured DNS servers or slow network responses, this resolution process can add significant delays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;ssh server.example.com &lt;span class="nb"&gt;date 
&lt;/span&gt;Warning: Reverse DNS lookup failed 
Tue Nov 26 10:15:32 UTC 2024 
real 0m3.245s 
user 0m0.035s 
sys 0m0.012s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output shows three timing measurements: 'real' indicates the actual elapsed wall-clock time (3.245 seconds), while 'user' and 'sys' show CPU time spent in user and kernel mode respectively. The large difference between real time (3.245s) and CPU time (0.047s total) indicates the connection is spending most of its time waiting for DNS resolution, not processing.&lt;/p&gt;

&lt;p&gt;We can significantly improve connection times by disabling DNS lookups in the server configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/ssh/sshd_config
&lt;span class="c"&gt;# Add or modify the following line&lt;/span&gt;
UseDNS no

&lt;span class="c"&gt;# Restart the SSH service to apply changes&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart sshd

&lt;span class="c"&gt;# Test the connection speed again&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;ssh server.example.com &lt;span class="nb"&gt;date
&lt;/span&gt;Tue Nov 26 10:15:32 UTC 2024
real    0m0.532s
user    0m0.034s
sys     0m0.011s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For connections over high-latency networks or when transferring large amounts of data, enabling SSH compression can yield substantial performance improvements. SSH compression becomes particularly effective when working with text-heavy sessions or transferring compressible data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.ssh/config
&lt;span class="c"&gt;# Global SSH client configuration&lt;/span&gt;
Host &lt;span class="k"&gt;*&lt;/span&gt;
    &lt;span class="c"&gt;# Enable compression for all connections&lt;/span&gt;
    Compression &lt;span class="nb"&gt;yes&lt;/span&gt;
    &lt;span class="c"&gt;# Use compression level 6 for optimal balance&lt;/span&gt;
    CompressionLevel 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perhaps one of the most powerful optimizations involves SSH connection multiplexing. Instead of establishing new TCP connections for each SSH session, multiplexing reuses an existing connection, dramatically reducing connection overhead. This becomes especially valuable when working with remote Git repositories or running multiple SSH sessions to the same server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.ssh/config
Host &lt;span class="k"&gt;*&lt;/span&gt;
    &lt;span class="c"&gt;# Enable automatic multiplexing&lt;/span&gt;
    ControlMaster auto
    &lt;span class="c"&gt;# Define the control socket location&lt;/span&gt;
    ControlPath ~/.ssh/control/%C
    &lt;span class="c"&gt;# Keep the master connection alive for an hour&lt;/span&gt;
    ControlPersist 1h
    &lt;span class="c"&gt;# Optional: Configure keepalive to prevent timeouts&lt;/span&gt;
    ServerAliveInterval 60
    ServerAliveCountMax 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can verify multiplexing is working by examining the control socket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; ~/.ssh/control/
total 0
drwx------ 2 user user 100 Nov 26 10:15 &lt;span class="nb"&gt;.&lt;/span&gt;
drwx------ 8 user user 160 Nov 26 10:15 ..
srw------- 1 user user   0 Nov 26 10:15 example.com-22-user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;srw&lt;/code&gt; at the start of the last line indicates this is a socket file (&lt;code&gt;s&lt;/code&gt;) with read-write permissions (&lt;code&gt;rw&lt;/code&gt;). The size showing as 0 is normal for socket files. The filename &lt;code&gt;example.com-22-user&lt;/code&gt; follows the format hostname-port-username, indicating an active multiplexed connection for this specific combination.&lt;/p&gt;

&lt;p&gt;The presence of the socket file indicates an active multiplexed connection. Subsequent SSH commands to the same host will reuse this connection, resulting in nearly instantaneous session establishment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;ssh server.example.com &lt;span class="nb"&gt;date
&lt;/span&gt;Tue Nov 26 10:15:32 UTC 2024
real    0m0.087s
user    0m0.012s
sys     0m0.008s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For production environments where consistent performance is critical, we might also consider adjusting &lt;code&gt;TCPkeepalive&lt;/code&gt; settings to prevent connection drops over problematic networks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Host production-&lt;span class="k"&gt;*&lt;/span&gt;
    &lt;span class="c"&gt;# More aggressive keepalive for production servers&lt;/span&gt;
    TCPKeepAlive &lt;span class="nb"&gt;yes
    &lt;/span&gt;ServerAliveInterval 30
    ServerAliveCountMax 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resolving SSH Key-Related Issues
&lt;/h3&gt;

&lt;p&gt;SSH key problems often emerge as some of the most perplexing authentication challenges. Let's explore two critical categories of key-related issues that frequently impact SSH connections.&lt;/p&gt;

&lt;p&gt;One of the most common SSH key errors presents itself with an alarming warning message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s1"&gt;'/home/user/.ssh/id_rsa'&lt;/span&gt; are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error reflects SSH's strict security requirements for private key files. Common scenarios leading to this issue include copying keys from another system, extracting them from backups, or creating them with incorrect default permissions. Let's examine a typical scenario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; ~/.ssh/id_rsa
&lt;span class="nt"&gt;-rw-rw-r--&lt;/span&gt; 1 user user 1876 Nov 26 10:15 /home/user/.ssh/id_rsa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The permissions shown above (664) allow group members to read the private key, creating a security vulnerability. We can resolve this by applying proper permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Secure the private key file&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.ssh/id_rsa
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; ~/.ssh/id_rsa
&lt;span class="nt"&gt;-rw-------&lt;/span&gt; 1 user user 1876 Nov 26 10:15 /home/user/.ssh/id_rsa

&lt;span class="c"&gt;# Secure the SSH directory itself&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;700 ~/.ssh
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; ~ | &lt;span class="nb"&gt;grep&lt;/span&gt; .ssh
drwx------ 2 user user 4096 Nov 26 10:15 .ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another subtle but frustrating issue occurs when SSH refuses to read seemingly valid key files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Load key &lt;span class="s2"&gt;"/home/user/.ssh/id_rsa"&lt;/span&gt;: invalid format
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error often surfaces after migrating keys between different SSH implementations or when working with keys generated by third-party tools. Let's investigate a problematic key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh-keygen &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; ~/.ssh/id_rsa
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: INVALID KEY FILE FORMAT DETECTED!           @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-l&lt;/code&gt; flag attempts to show the key's fingerprint and bit length. This error indicates the key file exists but isn't in a format SSH can parse. This often happens when the key file has been corrupted during transfer or when it's been modified by a text editor that changed line endings or character encoding.&lt;/p&gt;

&lt;p&gt;The error might occur because the key is in a modern OpenSSH format while connecting to an older server, or vice versa. We can examine the key's content (being careful not to expose private key material):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1 ~/.ssh/id_rsa
&lt;span class="nt"&gt;-----BEGIN&lt;/span&gt; OPENSSH PRIVATE KEY-----
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we see a different header format or unexpected content, we might need to convert the key to a compatible format. The PEM format offers the widest compatibility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Backup the original key first&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; ~/.ssh/id_rsa ~/.ssh/id_rsa.backup

&lt;span class="c"&gt;# Convert the key to PEM format&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;ssh-keygen &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; ~/.ssh/id_rsa &lt;span class="nt"&gt;-m&lt;/span&gt; PEM
Key has comment &lt;span class="s1"&gt;'user@hostname'&lt;/span&gt;
Enter new passphrase &lt;span class="o"&gt;(&lt;/span&gt;empty &lt;span class="k"&gt;for &lt;/span&gt;no passphrase&lt;span class="o"&gt;)&lt;/span&gt;: 
Enter same passphrase again: 
Your identification has been saved with the new passphrase.

&lt;span class="c"&gt;# Verify the key format&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1 ~/.ssh/id_rsa
&lt;span class="nt"&gt;-----BEGIN&lt;/span&gt; RSA PRIVATE KEY-----
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For keys that appear completely unreadable, we might need to check their encoding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;file ~/.ssh/id_rsa
/home/user/.ssh/id_rsa: PEM RSA private key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes, keys might become corrupted during transfer, especially when copying between Windows and Unix systems. In such cases, checking for hidden characters or incorrect line endings can help:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;dos2unix ~/.ssh/id_rsa
dos2unix: converting file /home/user/.ssh/id_rsa to Unix format...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Debugging Techniques
&lt;/h2&gt;

&lt;p&gt;When standard troubleshooting steps fall short, we often need to delve deeper into SSH's internal workings to identify and resolve complex connection issues. SSH provides sophisticated debugging capabilities that, while potentially overwhelming at first glance, offer invaluable insights into the connection process. These advanced techniques become particularly necessary when dealing with enterprise environments, complex network configurations, or when standard error messages prove insufficient for diagnosis. &lt;/p&gt;

&lt;p&gt;During production incidents or when supporting mission-critical systems, these debugging approaches help us understand the intricate dance between client and server configurations, network interactions, and authentication mechanisms. Let's explore the advanced tools and techniques that experienced system administrators rely on for resolving challenging SSH connection problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verbose Logging
&lt;/h3&gt;

&lt;p&gt;The very first step which should be done - enable excessive logging. SSH's logging capabilities represent one of our most powerful diagnostic tools, offering three distinct levels of detail (&lt;code&gt;-v&lt;/code&gt;, &lt;code&gt;-vv&lt;/code&gt;, &lt;code&gt;-vvv&lt;/code&gt;). Each level peels back another layer of the connection process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Basic debugging output&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;ssh &lt;span class="nt"&gt;-v&lt;/span&gt; user@hostname
OpenSSH_8.9p1 Ubuntu-3ubuntu0.1, OpenSSL 3.0.2 15 Mar 2022
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: Connecting to &lt;span class="nb"&gt;hostname &lt;/span&gt;port 22 &lt;span class="o"&gt;[&lt;/span&gt;192.168.1.100]

&lt;span class="c"&gt;# More detailed protocol debugging&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;ssh &lt;span class="nt"&gt;-vv&lt;/span&gt; user@hostname
debug2: resolving &lt;span class="s2"&gt;"hostname"&lt;/span&gt; port 22
debug2: ssh_connect_direct: needpriv 0
debug2: fd 3 setting O_NONBLOCK

&lt;span class="c"&gt;# Maximum verbosity for complex issues&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;ssh &lt;span class="nt"&gt;-vvv&lt;/span&gt; user@hostname
debug3: send packet: &lt;span class="nb"&gt;type &lt;/span&gt;5
debug3: receive packet: &lt;span class="nb"&gt;type &lt;/span&gt;6
debug3: rekey after 134217728 bytes, 3600 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Server-Side Logging
&lt;/h3&gt;

&lt;p&gt;Understanding what's happening on SSH server gives better vision of root cause of the problems, as well as security and troubleshooting. By enabling detailed logging, we can monitor authentication attempts, track user sessions, and investigate potential security incidents with precision.&lt;/p&gt;

&lt;p&gt;To unlock the full potential of SSH logging, at first we need to modify SSH daemon configuration. Open &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; and set the logging level to its most verbose setting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;LogLevel DEBUG3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SSH activity can be monitored in real-time through system logs. The log location varies by Linux distribution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# For Debian-based systems (Ubuntu, Debian)&lt;/span&gt;
&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/auth.log

&lt;span class="c"&gt;# For Red Hat-based systems (RHEL, CentOS, Fedora)&lt;/span&gt;
&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/secure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logs contain detailed information about SSH connections. Here's an example of a successful login with its associated IP address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Apr 15 14:23:21 server sshd[12345]: Accepted publickey &lt;span class="k"&gt;for &lt;/span&gt;alice from 192.168.1.100 port 52413
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Failed authentication attempts are also recorded, providing valuable security insights:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Apr 15 14:25:33 server sshd[12346]: Failed password &lt;span class="k"&gt;for &lt;/span&gt;invalid user admin from 203.0.113.1 port 59632 ssh2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log rotation helps manage the increased volume of data from DEBUG3 level logging. This can be configured in &lt;code&gt;/etc/logrotate.d/sshd&lt;/code&gt; to maintain disk space while preserving historical data.&lt;/p&gt;

&lt;p&gt;Note that verbose logging creates additional system overhead. In high-traffic production environments, consider reducing the log level after completing specific monitoring or investigation tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Connectivity
&lt;/h3&gt;

&lt;p&gt;Before diving into complex SSH issues, establishing basic connectivity helps narrow down potential problems. Let's start by examining network paths and connections.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;netcat&lt;/code&gt; utility provides a straightforward way to verify if the SSH port accepts connections:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nc &lt;span class="nt"&gt;-zv&lt;/span&gt; &lt;span class="nb"&gt;hostname &lt;/span&gt;22
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-z&lt;/code&gt; flag tells netcat to scan for listening daemons without sending data, while &lt;code&gt;-v&lt;/code&gt; enables verbose output. A successful response looks like 'Connection to hostname port 22 succeeded!', while a failure might show 'Connection refused' or 'Connection timed out'. This test confirms basic TCP connectivity without attempting SSH authentication.&lt;/p&gt;

&lt;p&gt;When connection issues arise, tracing the network path often reveals routing problems or blocked ports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;traceroute &lt;span class="nb"&gt;hostname&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DNS resolution problems can manifest as connection failures, so checking name resolution adds another layer of verification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig &lt;span class="nb"&gt;hostname&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moving beyond basic connectivity, validating SSH configuration prevents common setup issues. The SSH client includes built-in configuration testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-G&lt;/span&gt; &lt;span class="nb"&gt;hostname&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command displays the exact configuration that will be used when connecting to the specified host, including inherited defaults and matching Host blocks.&lt;/p&gt;

&lt;p&gt;For server-side verification, the SSH daemon offers similar diagnostic capabilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;sshd &lt;span class="nt"&gt;-T&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command performs a comprehensive check of the server configuration, displaying the active settings after processing all included files and applying default values. The output helps identify misconfigurations before they impact users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for SSH Troubleshooting
&lt;/h2&gt;

&lt;p&gt;When troubleshooting SSH issues, following a methodical approach leads to faster resolution. Starting with basic connectivity checks establishes a foundation for further investigation. Moving through permission verification and authentication methods helps isolate problems systematically. System logs and verbose SSH output often reveal the root cause of connection issues.&lt;/p&gt;

&lt;p&gt;Maintaining clear documentation strengthens our troubleshooting capabilities. Recording configuration changes, preserving working configurations, and keeping configuration backups creates a reliable reference point when issues arise. This documentation becomes particularly valuable when dealing with complex multi-server environments.&lt;/p&gt;

&lt;p&gt;During troubleshooting, maintaining security remains paramount. Avoiding temporary security bypasses prevents accidental exposure. Host key changes warrant careful verification, and proper file permissions must be maintained throughout the debugging process.&lt;/p&gt;

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

&lt;p&gt;SSH debugging requires a methodical approach and understanding of both the protocol and common failure points. By following this guide, you can efficiently diagnose and resolve SSH connection issues while maintaining security. Remember that SSH's complexity is a feature, not a bug – it's designed to be secure first and convenient second.&lt;/p&gt;

&lt;p&gt;Future maintenance of SSH connections can be simplified by implementing proper monitoring, maintaining documentation, and following security best practices. When issues do arise, a systematic debugging approach will help resolve them quickly and effectively.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ssh</category>
      <category>devops</category>
      <category>aws</category>
    </item>
    <item>
      <title>Understanding SSH Key Pairs: A Developer's Guide</title>
      <dc:creator>Eugene Zimin</dc:creator>
      <pubDate>Tue, 26 Nov 2024 07:22:45 +0000</pubDate>
      <link>https://dev.to/eugene-zimin/understanding-ssh-key-pairs-a-developers-guide-2eoo</link>
      <guid>https://dev.to/eugene-zimin/understanding-ssh-key-pairs-a-developers-guide-2eoo</guid>
      <description>&lt;p&gt;In today's interconnected development world, secure authentication is not just a luxury—it's a necessity. Whether you're a seasoned DevOps engineer or a junior developer just starting your journey, understanding SSH key pairs is crucial for your daily workflow. They're the unsung heroes that keep our git pushes secure, our server access protected, and our deployments safe from prying eyes.&lt;/p&gt;

&lt;p&gt;But let's be honest: SSH keys can be confusing. With terms like "public key infrastructure," "cryptographic algorithms," and "key fingerprints" floating around, it's easy to feel overwhelmed. This guide aims to demystify SSH key pairs, breaking down complex concepts into digestible pieces that will help you make informed decisions about your security setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are SSH Key Pairs?
&lt;/h2&gt;

&lt;p&gt;SSH key pairs are cryptographic credentials consisting of two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A private key that stays on your local machine (keep this secret!)&lt;/li&gt;
&lt;li&gt;A public key that you can share freely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of them as a sophisticated lock and key system. The public key is like a special lock you can give to anyone, while the private key is the unique key that opens only your locks. When you connect to a remote system, it checks if your private key matches the public key it has stored—if they match, you're granted access. This eliminates the need for password-based authentication, which can be vulnerable to brute force attacks and keyloggers.&lt;/p&gt;

&lt;p&gt;What makes SSH key pairs particularly powerful is their ability to provide secure authentication without transmitting sensitive information over the network. Your private key never leaves your machine, making it virtually impossible for attackers to intercept your credentials during the authentication process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of SSH Key Pairs and Their Mathematical Foundations
&lt;/h2&gt;

&lt;p&gt;The cryptographic algorithms behind SSH keys represent some of the most elegant applications of number theory and algebraic geometry in modern computing. Let's delve deep into their mathematical foundations and understand how they provide the security we rely on daily.&lt;/p&gt;

&lt;h3&gt;
  
  
  RSA: The Prime Numbers Guardian
&lt;/h3&gt;

&lt;p&gt;RSA's brilliance lies in the elegant use of prime numbers and modular arithmetic. Let's walk through a simplified but illustrative example of how RSA actually works in practice.&lt;/p&gt;

&lt;p&gt;Suppose we want to create a small (insecure, but educational) RSA key:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;First, choose two prime numbers:&lt;br&gt;
p = 61 and q = 53&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Calculate n (the modulus):&lt;br&gt;
n = p × q = 61 × 53 = 3,233&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Calculate the totient φ(n):&lt;br&gt;
φ(n) = (p-1) × (q-1) = 60 × 52 = 3,120&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose a public exponent e (commonly 65537): &lt;br&gt;
Let's use e = 17 for this example (must be coprime with φ(n))&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Calculate the private exponent d:&lt;br&gt;
d = e⁻¹ mod φ(n) = 2,753&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public key: (n=3,233, e=17)&lt;/li&gt;
&lt;li&gt;Private key: (n=3,233, d=2,753)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how the encryption works with these numbers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Message (m) = 123
Encryption: c = m^e mod n
c = 123^17 mod 3,233 = 855

Decryption: m = c^d mod n
m = 855^2,753 mod 3,233 = 123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In real RSA implementations, we use numbers that are typically 2048 or 4096 bits long. To put this in perspective, a 4096-bit number is roughly 1,234 decimal digits long. The security comes from the fact that factoring such large numbers is computationally infeasible with current technology.&lt;/p&gt;

&lt;p&gt;Here's how you'd generate such a key in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a 4096-bit RSA key with custom settings&lt;/span&gt;
ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; rsa &lt;span class="nt"&gt;-b&lt;/span&gt; 4096 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"RSA-4096_&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; ~/.ssh/id_rsa_4096 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="s2"&gt;"your_secure_passphrase"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ed25519: Elliptic Curve Elegance
&lt;/h3&gt;

&lt;p&gt;Ed25519 operates on a specialized Edwards curve, defined by the equation:&lt;br&gt;
-x² + y² = 1 - (121665/121666)x²y²&lt;/p&gt;

&lt;p&gt;This curve was carefully chosen for several mathematical properties that make it both secure and efficient. Let's break down how a point multiplication works on this curve:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The base point B has coordinates:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x = 15112221349535400772501151409588531511454012693041857206046113283949847762202
y = 46316835694926478169428394003475163141307993866256225615783033603165251855960
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;A private key is a 256-bit scalar k&lt;/li&gt;
&lt;li&gt;The public key is the point k·B (scalar multiplication)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's a simplified example of point addition on the curve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Given two points P1(x₁,y₁) and P2(x₂,y₂):

x₃ = (x₁y₂ + y₁x₂)/(1 + dx₁x₂y₁y₂)
y₃ = (y₁y₂ - ax₁x₂)/(1 - dx₁x₂y₁y₂)

where d = -121665/121666
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual Ed25519 implementation uses field arithmetic modulo the prime 2²⁵⁵ - 19, chosen for efficient computation on modern 64-bit processors. When you generate an Ed25519 key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate Ed25519 key with maximum entropy&lt;/span&gt;
ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"Ed25519_&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; ~/.ssh/id_ed25519 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 32 /dev/urandom | &lt;span class="nb"&gt;base64&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real power of Ed25519 comes from its resistance to various implementation attacks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Signing equation:
R = rB
S = (r + H(R,A,M)a) mod l

where:
r = H(h_b,...,h_2b-1,M)
H = SHA-512
a = private key
A = public key
M = message
l = 2²⁵² + 27742317777372353535851937790883648493
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ECDSA: The NIST Curves
&lt;/h3&gt;

&lt;p&gt;ECDSA uses the NIST P-curves, which are defined over prime fields. The P-256 curve is defined by:&lt;br&gt;
y² = x³ - 3x + b&lt;/p&gt;

&lt;p&gt;where b = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B&lt;/p&gt;

&lt;p&gt;Let's examine a point multiplication on this curve:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with a base point G:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gx = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296
Gy = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;For a private key k, the public key Q = kG is computed through repeated point doubling and addition:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Point Addition (P + Q):
s = (y₂ - y₁)/(x₂ - x₁) mod p
x₃ = s² - x₁ - x₂ mod p
y₃ = s(x₁ - x₃) - y₁ mod p

Point Doubling (P + P):
s = (3x₁² + a)/(2y₁) mod p
x₃ = s² - 2x₁ mod p
y₃ = s(x₁ - x₃) - y₁ mod p
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Generate an ECDSA key using the P-521 curve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate ECDSA key with P-521 curve&lt;/span&gt;
ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ecdsa &lt;span class="nt"&gt;-b&lt;/span&gt; 521 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"ECDSA_P521_&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; ~/.ssh/id_ecdsa_521 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Extended Security Considerations
&lt;/h3&gt;

&lt;p&gt;The security of these algorithms depends on different hard mathematical problems:&lt;/p&gt;

&lt;p&gt;RSA: Integer Factorization Problem (IFP)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Given n = pq, find p and q
Time complexity: O(exp((log n)^(1/3) * (log log n)^(2/3)))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ed25519: Elliptic Curve Discrete Logarithm Problem (ECDLP)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Given P and Q = kP, find k
Time complexity: O(√n) using Pollard's rho algorithm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ECDSA: Same as Ed25519, but with different curve parameters&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Security level comparison:
RSA 3072-bit ≈ ECDSA/Ed25519 256-bit
RSA 15360-bit ≈ ECDSA/Ed25519 512-bit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Quantum Computing Impact
&lt;/h3&gt;

&lt;p&gt;The advent of quantum computers poses different threats to these algorithms. Using Shor's algorithm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RSA factoring time complexity:
Classical: O(exp((log N)^(1/3) * (log log N)^(2/3)))
Quantum: O((log N)^3)

ECDLP solving time complexity:
Classical: O(√n)
Quantum: O((log n)^3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why post-quantum cryptography is becoming increasingly important, though it's not yet implemented in standard SSH keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making an Informed Choice
&lt;/h2&gt;

&lt;p&gt;The choice between these algorithms goes beyond mathematics—it's about balancing security, compatibility, and performance. Ed25519 represents the future: mathematically elegant, computationally efficient, and designed with modern threats in mind. Its implementation provides consistent security properties across different platforms, making it the ideal choice for new deployments.&lt;/p&gt;

&lt;p&gt;For systems requiring broad compatibility, RSA with 4096 bits remains a solid choice. Its mathematical foundation has withstood decades of cryptanalysis, and while it may be computationally more intensive than modern elliptic curve approaches, its security margins are well understood.&lt;/p&gt;

&lt;p&gt;When implementing any of these algorithms, the key is to ensure proper entropy during key generation. A strong random number generator is crucial for security, as even the most mathematically secure algorithm can be compromised by poor randomness. Modern systems use hardware random number generators and entropy pools to ensure strong key generation, but it's worth being aware of this critical foundation.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ssh</category>
      <category>remote</category>
      <category>devops</category>
    </item>
    <item>
      <title>SSH Config File - Forgotten Gem</title>
      <dc:creator>Eugene Zimin</dc:creator>
      <pubDate>Tue, 26 Nov 2024 07:04:16 +0000</pubDate>
      <link>https://dev.to/eugene-zimin/ssh-config-file-forgotten-gem-1339</link>
      <guid>https://dev.to/eugene-zimin/ssh-config-file-forgotten-gem-1339</guid>
      <description>&lt;p&gt;For developers and system administrators managing multiple remote servers, the conventional approach of typing lengthy SSH commands such as those incorporating identity files, usernames, and complex domain names presents a significant operational burden. Consider the following typical command structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/special_key.pem username@ec2-123-45-67-89.compute-1.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Such verbose commands, while explicit in their intent, can be transformed into more elegant alternatives through proper configuration. The SSH config file enables concise commands like &lt;code&gt;ssh staging&lt;/code&gt; or &lt;code&gt;ssh prod&lt;/code&gt;, reducing cognitive load and potential typing errors. This often overlooked tool enhances SSH workflow efficiency substantially, while maintaining the robust security measures inherent in SSH protocols.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the SSH Config File
&lt;/h2&gt;

&lt;p&gt;The SSH config file, generally located at &lt;code&gt;~/.ssh/config&lt;/code&gt;, serves as a sophisticated configuration repository for SSH connections, embodying the Unix philosophy of maintaining simple, text-based configuration files. It functions as a sophisticated bookmark system with extensive customization capabilities, allowing for the definition of aliases and default settings. This approach to configuration management reflects the fundamental principles of systems administration: clarity, maintainability, and scalability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Configuration Implementation
&lt;/h2&gt;

&lt;p&gt;Consider this foundational example of an SSH config file (&lt;code&gt;~/.ssh/config&lt;/code&gt;), which demonstrates the essential elements of host configuration. Each section defines a specific connection profile, encapsulating all necessary parameters for establishing secure connections:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Host github
    HostName github.com
    User git
    IdentityFile ~/.ssh/github_key

Host staging
    HostName ec2-123-45-67-89.compute-1.amazonaws.com
    User ubuntu
    IdentityFile ~/.ssh/staging.pem
    Port 22
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration reduces verbose SSH commands to simplified versions, demonstrating the principle of abstraction in system administration. The complex underlying connection details remain hidden yet accessible when needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh github
&lt;span class="c"&gt;# or&lt;/span&gt;
ssh staging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Features and Implementations
&lt;/h2&gt;

&lt;p&gt;The SSH configuration system provides several sophisticated mechanisms for managing complex connection scenarios. These advanced features demonstrate the extensive capabilities of OpenSSH's configuration framework and its ability to handle diverse operational requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wildcard Pattern Implementation
&lt;/h2&gt;

&lt;p&gt;Pattern matching facilitates efficient host management through the implementation of glob-style patterns, enabling sophisticated matching rules that apply configurations across multiple hosts. This pattern-based approach significantly reduces configuration redundancy and maintains consistency across similar environments. The pattern matching system employs asterisks (*) and question marks (?) as wildcards, following similar principles to shell globbing patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Pattern Examples
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Development environment configurations&lt;/span&gt;
Host dev-&lt;span class="k"&gt;*&lt;/span&gt;
    User development-user
    IdentityFile ~/.ssh/dev_key.pem
    ForwardAgent &lt;span class="nb"&gt;yes
    &lt;/span&gt;StrictHostKeyChecking ask
    LogLevel INFO
    Port 22000

&lt;span class="c"&gt;# Staging environment configurations&lt;/span&gt;
Host staging-&lt;span class="k"&gt;*&lt;/span&gt;
    User staging-user
    IdentityFile ~/.ssh/staging_key.pem
    ForwardAgent &lt;span class="nb"&gt;yes
    &lt;/span&gt;StrictHostKeyChecking ask
    LogLevel INFO
    Port 22001

&lt;span class="c"&gt;# Production environment configurations&lt;/span&gt;
Host prod-&lt;span class="k"&gt;*&lt;/span&gt;
    User production-user
    IdentityFile ~/.ssh/prod_key.pem
    ForwardAgent no
    StrictHostKeyChecking &lt;span class="nb"&gt;yes
    &lt;/span&gt;LogLevel ERROR
    Port 22
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Advanced Pattern Matching
&lt;/h3&gt;

&lt;p&gt;The pattern matching system supports complex configurations through hierarchical rule application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Default settings for all hosts&lt;/span&gt;
Host &lt;span class="k"&gt;*&lt;/span&gt;
    Compression &lt;span class="nb"&gt;yes
    &lt;/span&gt;TCPKeepAlive &lt;span class="nb"&gt;yes
    &lt;/span&gt;ServerAliveInterval 60
    ForwardAgent no

&lt;span class="c"&gt;# Regional data center configurations&lt;/span&gt;
Host &lt;span class="k"&gt;*&lt;/span&gt;.eu-west-&lt;span class="k"&gt;*&lt;/span&gt;
    ProxyCommand ssh eu-jumphost &lt;span class="nt"&gt;-W&lt;/span&gt; %h:%p
    User european-user
    IdentityFile ~/.ssh/eu_key.pem

Host &lt;span class="k"&gt;*&lt;/span&gt;.us-east-&lt;span class="k"&gt;*&lt;/span&gt;
    ProxyCommand ssh us-jumphost &lt;span class="nt"&gt;-W&lt;/span&gt; %h:%p
    User american-user
    IdentityFile ~/.ssh/us_key.pem

&lt;span class="c"&gt;# Service-specific patterns&lt;/span&gt;
Host db-&lt;span class="k"&gt;*&lt;/span&gt;
    User database-admin
    Port 5022
    StrictHostKeyChecking &lt;span class="nb"&gt;yes

&lt;/span&gt;Host app-&lt;span class="k"&gt;*&lt;/span&gt;
    User application-admin
    Port 5023
    StrictHostKeyChecking &lt;span class="nb"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern Precedence Examples
&lt;/h3&gt;

&lt;p&gt;The pattern matching system follows a hierarchical precedence model, where more specific patterns override general ones. This enables sophisticated configuration layering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Global defaults&lt;/span&gt;
Host &lt;span class="k"&gt;*&lt;/span&gt;
    ForwardAgent no
    Compression &lt;span class="nb"&gt;yes
    &lt;/span&gt;ServerAliveInterval 60

&lt;span class="c"&gt;# Environment-specific overrides&lt;/span&gt;
Host &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="nt"&gt;-prod&lt;/span&gt;
    ForwardAgent no
    Compression no
    ServerAliveInterval 120
    StrictHostKeyChecking &lt;span class="nb"&gt;yes&lt;/span&gt;

&lt;span class="c"&gt;# Region-specific overrides&lt;/span&gt;
Host &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="nt"&gt;-prod-eu&lt;/span&gt;
    ProxyCommand ssh eu-prod-bastion &lt;span class="nt"&gt;-W&lt;/span&gt; %h:%p
    IdentityFile ~/.ssh/eu_prod_key.pem

&lt;span class="c"&gt;# Service-specific configurations within production&lt;/span&gt;
Host db-&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="nt"&gt;-prod&lt;/span&gt;
    User database-prod-admin
    Port 5022
    IdentityFile ~/.ssh/db_prod_key.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real-world Implementation Examples
&lt;/h3&gt;

&lt;p&gt;Consider a multi-environment, multi-region infrastructure setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Development environments&lt;/span&gt;
Host dev-app-&lt;span class="k"&gt;*&lt;/span&gt; dev-db-&lt;span class="k"&gt;*&lt;/span&gt;
    User devops
    IdentityFile ~/.ssh/dev_key.pem
    ForwardAgent &lt;span class="nb"&gt;yes
    &lt;/span&gt;StrictHostKeyChecking no

    &lt;span class="c"&gt;# Development-specific settings&lt;/span&gt;
    LogLevel DEBUG
    Compression &lt;span class="nb"&gt;yes
    &lt;/span&gt;TCPKeepAlive &lt;span class="nb"&gt;yes
    &lt;/span&gt;ServerAliveInterval 30

&lt;span class="c"&gt;# Regional production configurations&lt;/span&gt;
Host prod-app-eu-&lt;span class="k"&gt;*&lt;/span&gt; prod-db-eu-&lt;span class="k"&gt;*&lt;/span&gt;
    User prod-admin
    IdentityFile ~/.ssh/prod_eu_key.pem
    ProxyCommand ssh eu-prod-bastion &lt;span class="nt"&gt;-W&lt;/span&gt; %h:%p

    &lt;span class="c"&gt;# Production-specific settings&lt;/span&gt;
    LogLevel ERROR
    Compression no
    TCPKeepAlive no
    ServerAliveInterval 60
    StrictHostKeyChecking &lt;span class="nb"&gt;yes&lt;/span&gt;

&lt;span class="c"&gt;# Database-specific configurations&lt;/span&gt;
Host &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="nt"&gt;-db-&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;
    &lt;span class="c"&gt;# Database server specific settings&lt;/span&gt;
    Port 5022
    IPQoS throughput
    Ciphers aes256-gcm@openssh.com,aes128-gcm@openssh.com

&lt;span class="c"&gt;# Application-specific configurations&lt;/span&gt;
Host &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="nt"&gt;-app-&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;
    &lt;span class="c"&gt;# Application server specific settings&lt;/span&gt;
    Port 5023
    IPQoS lowdelay
    Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com

&lt;span class="c"&gt;# Monitoring system access&lt;/span&gt;
Host monitor-&lt;span class="k"&gt;*&lt;/span&gt;
    User monitoring
    IdentityFile ~/.ssh/monitoring_key.pem
    PermitLocalCommand &lt;span class="nb"&gt;yes
    &lt;/span&gt;LocalCommand logger &lt;span class="s2"&gt;"Monitoring system access: %h"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This comprehensive pattern matching implementation enables:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Environment segregation (development, staging, production)&lt;/li&gt;
&lt;li&gt;Regional configuration management&lt;/li&gt;
&lt;li&gt;Service-specific settings&lt;/li&gt;
&lt;li&gt;Security policy enforcement&lt;/li&gt;
&lt;li&gt;Performance optimization per service type&lt;/li&gt;
&lt;li&gt;Audit logging for specific access patterns&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The pattern matching system proves particularly valuable in large-scale infrastructures where maintaining individual host entries would become unmanageable. Through careful pattern design, administrators can implement consistent policies while maintaining the flexibility to override specific settings where necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  ProxyJump Configuration for Bastion Hosts
&lt;/h2&gt;

&lt;p&gt;Bastion host traversal becomes straightforward through proper configuration, implementing the security principle of defense in depth. This approach enables secure access to internal resources while maintaining strict access controls. The ProxyJump feature, introduced in OpenSSH 7.3, replaces the older ProxyCommand methodology:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Bastion host configuration&lt;/span&gt;
Host bastion
    HostName bastion.company.com
    User jumpuser
    IdentityFile ~/.ssh/bastion_key
    StrictHostKeyChecking &lt;span class="nb"&gt;yes
    &lt;/span&gt;LogLevel VERBOSE

&lt;span class="c"&gt;# Internal server accessed via bastion&lt;/span&gt;
Host internal-server
    HostName 10.0.0.5
    User appuser
    ProxyJump bastion
    IdentityFile ~/.ssh/internal_key
    ForwardAgent no
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more complex scenarios, multiple jump hosts can be specified in sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Multi-hop bastion configuration&lt;/span&gt;
Host internal-private
    HostName 192.168.1.100
    ProxyJump bastion1,bastion2
    User internal-user
    IdentityFile ~/.ssh/internal_key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Connection Persistence Configuration
&lt;/h2&gt;

&lt;p&gt;Optimizing connection management and minimizing authentication requests through persistent connections represents a significant improvement in both security and efficiency. The ControlMaster feature enables multiple SSH sessions to share a single network connection, reducing overhead and improving response times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Host &lt;span class="k"&gt;*&lt;/span&gt;
    &lt;span class="c"&gt;# Connection sharing configuration&lt;/span&gt;
    ControlMaster auto
    ControlPath ~/.ssh/control/%C
    ControlPersist 1h

    &lt;span class="c"&gt;# Connection keepalive settings&lt;/span&gt;
    ServerAliveInterval 60
    ServerAliveCountMax 3

    &lt;span class="c"&gt;# TCP keepalive and compression&lt;/span&gt;
    TCPKeepAlive &lt;span class="nb"&gt;yes
    &lt;/span&gt;Compression &lt;span class="nb"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration implements several important features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ControlMaster auto&lt;/strong&gt;: Automatically creates a master connection for subsequent sharing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ControlPath&lt;/strong&gt;: Defines the socket file location using %C for a unique hash of connection parameters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ControlPersist&lt;/strong&gt;: Maintains the master connection in the background for the specified duration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The implementation can be further enhanced with environment-specific adjustments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Development environments - longer persistence&lt;/span&gt;
Host dev-&lt;span class="k"&gt;*&lt;/span&gt;
    ControlPersist 4h
    ServerAliveInterval 30

&lt;span class="c"&gt;# Production environments - stricter settings&lt;/span&gt;
Host prod-&lt;span class="k"&gt;*&lt;/span&gt;
    ControlPersist 30m
    ServerAliveInterval 90
    ServerAliveCountMax 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Authentication Configurations
&lt;/h2&gt;

&lt;p&gt;The SSH config file supports sophisticated authentication mechanisms, including multi-factor authentication and certificate-based access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host secure-server
    HostName secure.company.com
    User secure-user
    CertificateFile ~/.ssh/user_cert.pub
    IdentityFile ~/.ssh/secure_key
    PKCS11Provider /usr/local/lib/opensc-pkcs11.so
    RequestTTY force
    PreferredAuthentications publickey,keyboard-interactive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Port Forwarding and Tunnel Configuration
&lt;/h2&gt;

&lt;p&gt;Advanced port forwarding configurations enable secure access to remote services while maintaining security boundaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Host tunnel-host
    HostName gateway.company.com
    User tunnel-user
    &lt;span class="c"&gt;# Forward local port 8080 to remote host's port 80&lt;/span&gt;
    LocalForward 8080 internal.company.com:80
    &lt;span class="c"&gt;# Forward remote port 5432 to local PostgreSQL instance&lt;/span&gt;
    RemoteForward 5432 localhost:5432
    &lt;span class="c"&gt;# Dynamic SOCKS proxy on local port 1080&lt;/span&gt;
    DynamicForward 1080
    &lt;span class="c"&gt;# Ensure tunnel stays active&lt;/span&gt;
    ExitOnForwardFailure &lt;span class="nb"&gt;yes
    &lt;/span&gt;ServerAliveInterval 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Configuration Patterns
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Service-Specific Key Management&lt;/strong&gt;&lt;br&gt;
   The implementation of distinct keys for different services enhances security through isolation and granular access control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   Host github.com
       IdentityFile ~/.ssh/github_key

   Host gitlab.com
       IdentityFile ~/.ssh/gitlab_key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Environment-Specific Configurations&lt;/strong&gt;&lt;br&gt;
   Different environments often require distinct logging levels and access patterns, reflecting the operational requirements of various deployment stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   Host prod-&lt;span class="k"&gt;*&lt;/span&gt;
       User prod-user
       LogLevel QUIET

   Host dev-&lt;span class="k"&gt;*&lt;/span&gt;
       User dev-user
       LogLevel VERBOSE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Port Configuration by Environment&lt;/strong&gt;&lt;br&gt;
   Security requirements often necessitate different port configurations across environments, implementing the principle of least privilege:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   Host staging-web
       HostName staging.company.com
       Port 2222

   Host prod-web
       HostName prod.company.com
       Port 22
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Security Implementation Guidelines
&lt;/h2&gt;

&lt;p&gt;The implementation of robust security measures in SSH configuration requires a methodical approach to multiple aspects of system security. Each element of the configuration must be carefully considered to maintain the integrity and confidentiality of SSH connections while ensuring operational efficiency.&lt;/p&gt;

&lt;h3&gt;
  
  
  File System Security
&lt;/h3&gt;

&lt;p&gt;The cornerstone of SSH security begins with proper file system permissions. These permissions form the first line of defense against unauthorized access and potential security breaches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set restrictive permissions on SSH directory&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;700 ~/.ssh

&lt;span class="c"&gt;# Set proper permissions on configuration file&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.ssh/config

&lt;span class="c"&gt;# Secure private keys&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.ssh/id_rsa
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.ssh/id_ed25519

&lt;span class="c"&gt;# Ensure public keys are readable&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;644 ~/.ssh/&lt;span class="k"&gt;*&lt;/span&gt;.pub

&lt;span class="c"&gt;# Protect known_hosts file&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.ssh/known_hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Management Protocols
&lt;/h3&gt;

&lt;p&gt;The implementation of a robust key management strategy requires careful consideration of several factors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example of key-specific configurations&lt;/span&gt;
Host production-&lt;span class="k"&gt;*&lt;/span&gt;
    &lt;span class="c"&gt;# Specify permitted key types&lt;/span&gt;
    PubkeyAcceptedKeyTypes ssh-ed25519,rsa-sha2-512

    &lt;span class="c"&gt;# Define preferred authentication methods&lt;/span&gt;
    PreferredAuthentications publickey

    &lt;span class="c"&gt;# Disable password authentication&lt;/span&gt;
    PasswordAuthentication no

    &lt;span class="c"&gt;# Restrict key forwarding&lt;/span&gt;
    ForwardAgent no

    &lt;span class="c"&gt;# Enable strict host key checking&lt;/span&gt;
    StrictHostKeyChecking &lt;span class="nb"&gt;yes&lt;/span&gt;

    &lt;span class="c"&gt;# Use specific identity file&lt;/span&gt;
    IdentityFile ~/.ssh/prod_%h_ed25519
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Network Security Configurations
&lt;/h3&gt;

&lt;p&gt;Implementation of network-level security measures helps protect against various attack vectors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Enhanced security configuration&lt;/span&gt;
Host &lt;span class="k"&gt;*&lt;/span&gt;
    &lt;span class="c"&gt;# Prefer modern, secure ciphers&lt;/span&gt;
    Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com

    &lt;span class="c"&gt;# Specify secure key exchange algorithms&lt;/span&gt;
    KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group16-sha512

    &lt;span class="c"&gt;# Define secure MAC algorithms&lt;/span&gt;
    MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com

    &lt;span class="c"&gt;# Disable X11 forwarding&lt;/span&gt;
    ForwardX11 no

    &lt;span class="c"&gt;# Set connection timeout&lt;/span&gt;
    ConnectTimeout 60

    &lt;span class="c"&gt;# Enable verbose logging for security events&lt;/span&gt;
    LogLevel VERBOSE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Environment-Specific Security Policies
&lt;/h3&gt;

&lt;p&gt;Different environments require varying levels of security controls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Development environment
Host dev-*
    StrictHostKeyChecking ask
    UserKnownHostsFile ~/.ssh/known_hosts.dev
    LogLevel DEBUG3

# Staging environment
Host staging-*
    StrictHostKeyChecking yes
    UserKnownHostsFile ~/.ssh/known_hosts.staging
    LogLevel VERBOSE

# Production environment
Host prod-*
    StrictHostKeyChecking yes
    UserKnownHostsFile ~/.ssh/known_hosts.prod
    LogLevel ERROR
    IdentitiesOnly yes
    MaxAuthTries 3
    NoHostAuthenticationForLocalhost no
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Secret Management Integration
&lt;/h3&gt;

&lt;p&gt;Modern security practices often involve integration with external secret management systems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example of retrieving credentials securely&lt;/span&gt;
Host secure-service
    &lt;span class="c"&gt;# Use environment variables for sensitive data&lt;/span&gt;
    Match &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"test -n '&lt;/span&gt;&lt;span class="nv"&gt;$SSH_SECRET_KEY_PATH&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;
        IdentityFile &lt;span class="nv"&gt;$SSH_SECRET_KEY_PATH&lt;/span&gt;

    &lt;span class="c"&gt;# Integration with external secret management&lt;/span&gt;
    Match &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"vault-ssh-helper --verify"&lt;/span&gt;
        IdentityFile ~/.ssh/vault-signed-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Audit and Logging Configurations
&lt;/h3&gt;

&lt;p&gt;Implementing comprehensive logging helps in security monitoring and incident response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Host &lt;span class="k"&gt;*&lt;/span&gt;
    &lt;span class="c"&gt;# Enable detailed logging&lt;/span&gt;
    LogLevel VERBOSE

    &lt;span class="c"&gt;# Log connection attempts&lt;/span&gt;
    Match &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"logger 'SSH connection attempt to %h'"&lt;/span&gt;
        LogLevel DEBUG

    &lt;span class="c"&gt;# Additional security logging&lt;/span&gt;
    PermitLocalCommand &lt;span class="nb"&gt;yes
    &lt;/span&gt;LocalCommand logger &lt;span class="s2"&gt;"SSH connection established to %h by %r"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The SSH config file represents a powerful tool for optimizing SSH workflows and implementing robust security practices. Through proper configuration, complex SSH commands can be condensed into efficient, memorable aliases while maintaining comprehensive security standards. This approach exemplifies the balance between usability and security in systems administration.&lt;/p&gt;

&lt;p&gt;The management of multiple servers becomes considerably more efficient through effective SSH config file utilization. Beginning with basic host configurations and progressively implementing more advanced features allows for natural skill progression and workflow enhancement, following the principle of incremental improvement in systems administration.&lt;/p&gt;

&lt;p&gt;It is worth noting that the examples presented constitute only a subset of the available functionality. The official OpenSSH documentation provides extensive additional options for advanced configuration and customization, offering numerous possibilities for further optimization and security enhancement.&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>remote</category>
      <category>cloud</category>
      <category>access</category>
    </item>
    <item>
      <title>Access to Google Cloud Virtual Machine through SSH</title>
      <dc:creator>Eugene Zimin</dc:creator>
      <pubDate>Tue, 26 Nov 2024 05:30:06 +0000</pubDate>
      <link>https://dev.to/eugene-zimin/access-to-google-cloud-virtual-machine-through-ssh-8k9</link>
      <guid>https://dev.to/eugene-zimin/access-to-google-cloud-virtual-machine-through-ssh-8k9</guid>
      <description>&lt;p&gt;Earlier in another article, we provided a detailed description of how to create and set up a Virtual Machine (VM) instance in the Google Cloud Platform (GCP) using Google Compute Engine (GCE). It is accessible here - &lt;a href="https://medium.com/@eugene-zimin/google-cloud-provisioning-a-virtual-machine-and-accessing-it-via-ssh-dde4307a8e9b" rel="noopener noreferrer"&gt;Google Cloud: Provisioning a Virtual Machine and Accessing it via SSH&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Provisioning a GCP VM allows us to create our own powerful computing environment to serve as a sandbox or workspace for our further testing and experiments. Having successfully deployed a VM in GCP, the next crucial step is to establish a secure remote connection to this virtual machine. It is required by many reasons, starting from efficient administration and management up to the monitoring and observing it. Google Cloud Platform provides a straightforward method to configure SSH access to Compute Engine instances, facilitating an encrypted communication channel. In this article, we will outline the steps required to set up SSH access to a Google Compute Engine (GCE) virtual machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Few Words About SSH
&lt;/h2&gt;

&lt;p&gt;Secure Shell (SSH) is a cryptographic network protocol and the de facto standard for secure remote login and command execution on Unix-like operating systems. SSH provides a secure encrypted channel over an unsecured network, preventing potential eavesdropping, packet sniffing, or man-in-the-middle attacks that could compromise login credentials or sensitive data. By leveraging strong encryption algorithms and authenticated key exchange, SSH ensures data integrity and confidentiality, making it an essential tool for securely administering remote servers, transferring files, and managing networked systems over insecure networks such as the internet.&lt;/p&gt;

&lt;p&gt;SSH operates through the use of encrypted key pairs - a public key that gets placed on the remote server, and a private key that is kept secure by the client. The keys are very large numbers derived via a one-way mathematical function, making them virtually impossible to derive if intercepted during transmission.&lt;/p&gt;

&lt;p&gt;When initiating an SSH connection, the client and server negotiate a secure symmetrical encryption cipher and session keys to use. This key exchange utilizes the private/public keys for authentication and protection against man-in-the-middle attacks. Once the secure tunnel is established, all subsequent commands, file transfers, and communications are encrypted between the client and server.&lt;br&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%2Fcw2az2xkm7x20jvgjj1z.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%2Fcw2az2xkm7x20jvgjj1z.png" alt="Image description" width="800" height="1003"&gt;&lt;/a&gt;Figure 1. Communication schema for SSH exchange&lt;/p&gt;

&lt;p&gt;SSH supports several strong modern encryption algorithms such as AES, Blowfish, 3DES, and ciphers like ChaCha20. The specific algorithms used can be configured on both ends based on policy and requirements. SSH also provides data integrity verification through cryptographic message authentication codes like HMAC-SHA1 to detect tampering.&lt;/p&gt;

&lt;p&gt;By default, SSH operates on TCP port 22, but can be configured to use any port. It supports various user authentication mechanisms like passwords, public keys, security keys, and more. SSH key-based authentication is considered more secure than basic password authentication.&lt;/p&gt;
&lt;h2&gt;
  
  
  Configuring SSH on Local Machine
&lt;/h2&gt;

&lt;p&gt;Before initiating an SSH connection to our Google Cloud VM instance, we'll need to have SSH configured and set up on our local machine. The steps vary slightly depending on the operating system, as Linux and macOS usually have preinstalled SSH client which requires minimal configuration, whereas Window requires a bit more steps to make initial setup ready.&lt;/p&gt;
&lt;h4&gt;
  
  
  Linux and macOS
&lt;/h4&gt;

&lt;p&gt;Most Linux distributions and macOS come pre-installed with OpenSSH, a free open source SSH client and server utility. To check if it's installed, we can open a terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-V&lt;/span&gt;

&lt;span class="c"&gt;# Response should be something like the line below&lt;/span&gt;
&lt;span class="c"&gt;# OpenSSH_9.6p1, LibreSSL 3.3.6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should display the installed OpenSSH version. If not installed, we can get it through the operating system's package manager, for Linux it may be usually &lt;code&gt;apt&lt;/code&gt; or &lt;code&gt;yum&lt;/code&gt; for macOS &lt;code&gt;brew&lt;/code&gt; is very popular.&lt;/p&gt;

&lt;h4&gt;
  
  
  Windows
&lt;/h4&gt;

&lt;p&gt;Windows 11 has pre-installed SSH client. To check it we need to open &lt;code&gt;cmd&lt;/code&gt; application (a.k.a &lt;code&gt;Command Prompt&lt;/code&gt;) and run the same command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-V&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we will see a similar output we saw for Linux and macOS (some names may vary), it means we have installed SSH client and we are good to go.&lt;/p&gt;

&lt;p&gt;A little bit more complicated situation happens if Windows doesn't have preinstalled client. In that case we may use one of the following 3rd party applications to run it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.putty.org/" rel="noopener noreferrer"&gt;PuTTY&lt;/a&gt; for Windows&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-scm.com/download/win" rel="noopener noreferrer"&gt;Git Bash&lt;/a&gt; for Windows&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Generating Key Pairs
&lt;/h3&gt;

&lt;p&gt;To establish secure SSH connections, we need to generate cryptographic key pairs consisting of a public and private key. The &lt;code&gt;ssh-keygen&lt;/code&gt; utility allows us to create these key pairs locally on our machine. Before we start creating a key pair it may worth to overview file schema for the it.&lt;br&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%2Fixv1emprhbt4i04lqlp4.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%2Fixv1emprhbt4i04lqlp4.png" alt="Image description" width="800" height="747"&gt;&lt;/a&gt;Figure 2. Schema of SSH key pairs&lt;/p&gt;

&lt;p&gt;We can run &lt;code&gt;ssh-keygen&lt;/code&gt; in a terminal/command prompt and follow the prompts. Some common options include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-t&lt;/code&gt; to specify the key type (e.g. rsa, ecdsa, ed25519)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-b&lt;/code&gt; to set the key length in bits (e.g. 2048, 4096)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-C&lt;/code&gt; to add a comment to identify the key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; rsa &lt;span class="nt"&gt;-b&lt;/span&gt; 4096 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"user@example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates an RSA key pair with 4096 bits length associated with the &lt;a href="//mailto:user@example.com"&gt;user@example.com&lt;/a&gt; email.&lt;/p&gt;

&lt;p&gt;There are various key types we may use. Here are the most popular ones we may want to generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RSA&lt;/strong&gt; - One of the oldest and most widely used key types. Recommended minimum key length is 2048 bits, with 4096 bits being more secure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECDSA&lt;/strong&gt; (Elliptic Curve Digital Signature Algorithm) - This key type is based on elliptic curve cryptography which provides equal security strength with smaller key sizes compared to RSA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ed25519&lt;/strong&gt; - A modern EdDSA (Edwards-curve Digital Signature Algorithm) key type that provides better performance and security than RSA/ECDSA. Ed25519 uses elliptic curve cryptography with a 256-bit key length.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While RSA is the most widely compatible, Ed25519 keys are considered more secure and efficient for modern systems. We can check which key types our SSH server and client support using &lt;code&gt;ssh -Q key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;During key generation, we have the option to protect the private key with a passphrase. While providing a passphrase enhances security, we need to enter it every time the key is used.&lt;/p&gt;

&lt;p&gt;After generating, both keys - private and public - will be saved in the local SSH configuration folder. Usually (for Linux and macOS systems) this folder is under the following path - &lt;code&gt;~/.ssh/&lt;/code&gt;. Both keys by default use their name pattern as &lt;code&gt;id-&amp;lt;algorithm-name&amp;gt;[.pub]&lt;/code&gt; , where extension &lt;code&gt;.pub&lt;/code&gt; is used only for public key from the pair. As an example, user may get &lt;code&gt;id-rsa&lt;/code&gt; and &lt;code&gt;id-rsa.pub&lt;/code&gt; if there was RSA algorithm used, or &lt;code&gt;id-ed25519&lt;/code&gt; and &lt;code&gt;id-ed25519.pub&lt;/code&gt; there was Ed25519 algorithm in use during key pair generation.&lt;/p&gt;

&lt;p&gt;Next step would be to copy the content of the public key (which has &lt;code&gt;.pub&lt;/code&gt; extension) to the remote server, in accordance with the Figure 2. We may copy the content of the public key and paste it on the new line of the &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; file for the user which we will be connecting to. We can do that by many ways, but usually we should have access to the remote server via screen sharing or through the web interface, like with Google Compute Engine in GCP, where we can paste our public key.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE FOR GCP&lt;/strong&gt;&lt;br&gt;
GCP requires to add at the end of the key &lt;code&gt;userName&lt;/code&gt; and &lt;code&gt;expireOn&lt;/code&gt; in form of JSON. We should do that manually to get the ready for pasting. Eventually we should have something like that:&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;ecdsa-sha2-nistp521 AAAAE2...&lt;span class="o"&gt;==&lt;/span&gt; user@laptop.local &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"userName"&lt;/span&gt;:&lt;span class="s2"&gt;"&amp;lt;user@yourgcp.email&amp;gt;"&lt;/span&gt;,&lt;span class="s2"&gt;"expireOn"&lt;/span&gt;:&lt;span class="s2"&gt;"2024-05-06T08:43:20+0000"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;At the same time private key (without &lt;code&gt;.pub&lt;/code&gt; extension) remains securely stored on our local machine. We must keep the private key safe and never share it.&lt;/p&gt;

&lt;p&gt;With our key pair ready, we can now proceed to configure server to use key pair only and disallow further access using username and password.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring Server Side
&lt;/h3&gt;

&lt;p&gt;While using SSH keys provides a much more secure authentication mechanism compared to passwords, many cloud providers, including Google Cloud Platform, enable password-based logins to SSH servers by default. However, relying solely on password authentication for SSH connections introduces potential security risks and is considered a poor practice, so we should consider to disable it and keep it disabled.&lt;/p&gt;

&lt;p&gt;Main reasons why we should do that lay in the security considerations. Having password authentication enabled we allow anyone to try to connect to remote server and try out to brute force the password. Sometimes password are not that strong and such an attack might be successful.&lt;/p&gt;

&lt;p&gt;That being said the advantages of disabling Password authentication for the remote server are as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Security&lt;/strong&gt;: SSH keys utilize strong encryption algorithms and prevent brute-force and dictionary attacks that are common threats to password-based authentication. Keys are virtually impossible to guess or decrypt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eliminate Weak Passwords&lt;/strong&gt;: Enforcing key-based authentication eliminates the risk of users setting weak or easily guessable passwords, which is a common vulnerability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit Trail&lt;/strong&gt;: SSH key pairs provide better audit trails and monitoring capabilities, as each key can be associated with a specific user or system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance&lt;/strong&gt;: Many security standards and best practices, such as PCI-DSS, HIPAA, and NIST, recommend or mandate the use of key-based authentication over passwords for remote access to servers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At the same time nothing comes without price. If any of the parts for the key pair would be lost or corrupted, it means we may also lost the access to the server, thus we should be very careful and keep generated key pair as good as possible.&lt;/p&gt;

&lt;p&gt;To disable password authentication for the SSH server we need to modify the SSH daemon (&lt;code&gt;sshd&lt;/code&gt;) configuration file. To do that we need to connect to the VM instance over SSH. We will use our new generated private key to connect to the server (don't forget to replace username and address of the server).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/id_ed25519 remote-user@server-ip-address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the remote server open the SSH daemon configuration file as a &lt;code&gt;superuser&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/ssh/sshd_config &lt;span class="c"&gt;# may be changed to vim&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the file editor find the line &lt;code&gt;#PasswordAuthentication yes&lt;/code&gt; and change it to &lt;code&gt;PasswordAuthentication no&lt;/code&gt; and save the file and exit the editor.&lt;/p&gt;

&lt;p&gt;Finally we need to restart the SSH daemon to make our changes take effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart sshd &lt;span class="c"&gt;# (or command for your OS)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;br&gt;
After making this change, the SSH server will only allow key-based authentication, and password logins will be disabled.&lt;/p&gt;

&lt;p&gt;It's important to note that we should have at least one authorized SSH key added to the VM instance before disabling password authentication. Otherwise, we may get locked out of the system.&lt;/p&gt;

&lt;p&gt;By enforcing key-based authentication and disabling password logins, we significantly enhance the security of our SSH server and remote access to our Google Cloud virtual machines, aligning with industry best practices for secure system administration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuance
&lt;/h2&gt;

&lt;p&gt;The next article in the cycle is dedicated to the debugging and troubleshooting different connection errors happening during establishing SSH connections - &lt;a href="https://dev.to/eugene-zimin/debugging-ssh-connections-a-comprehensive-guide-1hoc"&gt;Debugging SSH connections: A Comprehensive Guide&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>ssh</category>
      <category>security</category>
      <category>development</category>
    </item>
    <item>
      <title>JWT at a Glance</title>
      <dc:creator>Eugene Zimin</dc:creator>
      <pubDate>Mon, 19 Aug 2024 04:32:11 +0000</pubDate>
      <link>https://dev.to/eugene-zimin/jwt-at-a-glance-4f1d</link>
      <guid>https://dev.to/eugene-zimin/jwt-at-a-glance-4f1d</guid>
      <description>&lt;p&gt;Many of us heard about JWT and while JWT has become a buzzword in tech circles, it's frequently misunderstood or confused with OAuth 2.0 and OIDC, particularly among those who use it without fully grasping its intricacies. Mixing up JWT with OAuth 2.0 and OIDC is like tossing different fruits into a blender and calling it all "smoothie." It's especially messy when folks treat these tech tools like magic wands, waving them around without peeking under the hood.&lt;/p&gt;

&lt;p&gt;What is JWT? By itself is a small piece of data that contains information about someone or something. It's like a small container which is able to keep and transfer information between two different destinations. Or it's like a digital badge that proves who the user is and what this user is authorized to do.&lt;/p&gt;

&lt;p&gt;Let's imagine a library where anyone could walk in and borrow books without showing any identification. It would be chaos! The library needs a way to know who's borrowing what, and to ensure that only members can borrow books. In the digital world, JWT serves a similar purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Session Management Based Authorization
&lt;/h2&gt;

&lt;p&gt;Traditional web applications often use sessions to keep track of who's logged in. This is like giving someone a special badge when they enter the library. Library staff can see the badge and then if they need to know details - they will check them out inside the computer. Let's take a look at the example below.&lt;br&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%2F8wbl1asbe6lt60g2iuqk.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%2F8wbl1asbe6lt60g2iuqk.png" alt="Figure 1. Regular request-response flow" width="800" height="115"&gt;&lt;/a&gt; Figure 1. Regular request-response flow&lt;/p&gt;

&lt;p&gt;This diagram illustrates the basic flow of a web application, where the browser sends a request to the server, the server processes the request (potentially interacting with a database), and then sends a response back to the browser. Nothing complex, it's just a business as usual:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User sends a request to the &lt;code&gt;Service&lt;/code&gt;, seeking for some information.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Service&lt;/code&gt; goes to the &lt;code&gt;Database&lt;/code&gt; to retrieve that data from the long term storage.&lt;/li&gt;
&lt;li&gt;Finally &lt;code&gt;Service&lt;/code&gt; returns that data to the user in his browser to view.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, let's imagine if the &lt;code&gt;Service&lt;/code&gt; holds sensitive personal information. Without security system to verify who's knocking on the door, there's a real danger of handing out private details to the wrong hands. It's like throwing a party and accidentally inviting the whole neighborhood when you meant to host a small gathering. In the digital world, this kind of mix-up isn't just embarrassing - it's a serious security risk that could expose confidential data to anyone who comes asking. That's a scenario is a "no-go" situation!&lt;/p&gt;

&lt;p&gt;To prevent this situation to ever happen, let's think about implementing another service, which would be in charge to authenticate users and grant them special permissions which data they allow to access and which is not. Look at the diagram below:&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%2Fgcpr4d5sjqunyz7p3tgx.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%2Fgcpr4d5sjqunyz7p3tgx.png" alt="Figure 2. Regular request-response flow with Login Service" width="800" height="266"&gt;&lt;/a&gt;Figure 2. Regular request-response flow with Login Service&lt;/p&gt;

&lt;p&gt;What is changed on the diagram? Let's take a look again. We've introduced a new service which will be in charge for user's authentication and authorization. In other words we make this service exists to ensure proper user verification before granting access to the system's resources.&lt;/p&gt;

&lt;p&gt;Prior to allowing any interaction with the data, this service requires users to authenticate themselves through a login process. Upon successful identification, the system initiates a session and issue a session identifier, or &lt;code&gt;SessionID&lt;/code&gt;. In this context, a session is essentially a database record in &lt;code&gt;Auth Users&lt;/code&gt; database indicating user is online and interacting with the system.&lt;/p&gt;

&lt;p&gt;Upon successful authentication, all the rest interaction with the system no longer require the user to re-authenticate. Instead, the user's client (browser) simply keeps and provides back with every request the session identifier, which is securely stored in the &lt;code&gt;Auth Users&lt;/code&gt; database. This identifier serves as a temporary credential, allowing the system to recognize and authorize the user for the duration of their session.&lt;/p&gt;

&lt;p&gt;It may be shown like on the diagram below.&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%2F2zqtjeykvysmwy4swxp2.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%2F2zqtjeykvysmwy4swxp2.png" alt="Figure 3. Session Management" width="800" height="376"&gt;&lt;/a&gt; Figure 3. Session Management&lt;/p&gt;

&lt;p&gt;Let's walk through this web application flow step by step, in a way that's easy to understand:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The process begins with the browser initiating an HTTP request to the server. This should happen only after user has been authenticated and have a &lt;code&gt;Session ID&lt;/code&gt; in hands as a response from the authentication flow.

&lt;ol&gt;
&lt;li&gt;HTTP request itself is comprehensive and contains at least:

&lt;ul&gt;
&lt;li&gt;The destination URL&lt;/li&gt;
&lt;li&gt;A token in the headers, any relevant cookies and optionally - a request body&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The token in the headers carries a &lt;code&gt;Session ID&lt;/code&gt; - a unique identifier acting as a digital passport for the user's session.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Upon receiving the request, the &lt;code&gt;Service&lt;/code&gt; doesn't immediately process it. Instead, it has to forwards the session information to the &lt;code&gt;Auth&lt;/code&gt; component for verification of the request sender, i.e user.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Auth&lt;/code&gt; service cross-references the &lt;code&gt;Session ID&lt;/code&gt; with the database, essentially checking the list of sessions and its mapping to the provided &lt;code&gt;Session ID&lt;/code&gt;. If there is a match, the &lt;code&gt;Auth&lt;/code&gt; component retrieves and returns the associated user data, which may include any necessary data about the user, like user ID, first and last name of the user, his email and user's role and permissions.&lt;/li&gt;
&lt;li&gt;Armed with this user information, &lt;code&gt;Service&lt;/code&gt; can now safely decide whether user is able to interact with the system or not. Eventually, &lt;code&gt;Service&lt;/code&gt; compiles the requested information and sends it back to the browser as an HTTP response if the user is authorized or rejects it if the user is not authorized.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it! As this process occurs with each interaction between user and the system, we may consider the system is safe unless the session token is not stolen or brute forced.&lt;/p&gt;

&lt;p&gt;Is that good or bad approach? There is no straightforward answer. One of its greatest benefits is that the backend fully controls the access to all protected resources. By any chance if any request would be considered suspicious, user might be easily kicked off of the system by erasing a record in the &lt;code&gt;Auth Users&lt;/code&gt; database with the &lt;code&gt;Session ID&lt;/code&gt; associated to him.&lt;/p&gt;

&lt;p&gt;On the other hand - backend needs to keep the state about user's status - whether he authenticated or not. This stateful nature of session-based authentication brings challenges.&lt;/p&gt;

&lt;p&gt;As every request to the system necessitates a database lookup to verify the session we can easily imagine what happens if our UI sends N (where N &amp;gt; 0) concurrent requests to retrieve information from the backend (hello React and other SPA):&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%2Fhg52p38lc4f9lj42sn14.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%2Fhg52p38lc4f9lj42sn14.png" alt="Figure 4. System Bottleneck" width="800" height="267"&gt;&lt;/a&gt;Figure 4. Potential bottleneck with the session management&lt;/p&gt;

&lt;p&gt;Take a look at the diagram above. On every user action, for example an initial page load, the UI may send 4 requests to fill out different page sections. That may happen as those data might be provided by different API Endpoints, like information about the user should be obtained from &lt;code&gt;/api/v1/users&lt;/code&gt; and information about their appointments from another one, like &lt;code&gt;/api/v1/appointments&lt;/code&gt;. More importantly, those requests are usually sent concurrently, as we want rapid page rendering in the user's browser.&lt;/p&gt;

&lt;p&gt;Now let's estimate the cost of such an approach. Every user action with that page will push us to send 4 concurrent requests to the &lt;code&gt;Service&lt;/code&gt;. Okay, that's doable. However each of those requests will also need be authorized, which means &lt;code&gt;Service&lt;/code&gt; should ask &lt;code&gt;Auth&lt;/code&gt; is that possible to proceed with them as it doesn't have any other information than &lt;code&gt;Session ID&lt;/code&gt; which is nothing more than a link in &lt;code&gt;Auth Users&lt;/code&gt; database. In that case &lt;code&gt;Service&lt;/code&gt; needs to issue another 4 requests to the &lt;code&gt;Auth&lt;/code&gt; and ask it about user information and details.&lt;/p&gt;

&lt;p&gt;Easy calculation gives us that 1 user action creates 4 requests to one service in our backend and same amount for another service on the backend. In total, these eight actions will all result in database interactions. Let's imagine there are another services which also require confirmed authorization and we will understand the full picture.&lt;/p&gt;

&lt;p&gt;Is it too much? The answer depends on the use case - sometimes it's necessary. However, is it possible to decrease the load on the database and other services?&lt;/p&gt;
&lt;h2&gt;
  
  
  Stateless Authorization Management
&lt;/h2&gt;

&lt;p&gt;There is another way to handle system security and authorize users without using session management. This alternative approach tackles some of the scalability headaches we saw earlier and it is often implemented using JSON Web Tokens (JWT).&lt;/p&gt;

&lt;p&gt;As we mentioned earlier JWT is a small container which holds some information for us. Compare it with the &lt;code&gt;Session ID&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%2Fwixqd8ycjvntrojx9mpy.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%2Fwixqd8ycjvntrojx9mpy.png" alt="Figure 5. Compare Sessions with JWT approaches" width="800" height="267"&gt;&lt;/a&gt;Figure 5. Compare Sessions with JWT approaches&lt;/p&gt;

&lt;p&gt;In a JWT-based system, the server no longer needs to store session information. Instead, when user authenticates, the &lt;code&gt;Auth&lt;/code&gt; generates a JWT - a compact, self-contained token that encapsulates all necessary user information and permissions. This token is then sent back to the client and stored, typically in local storage or a cookie.&lt;/p&gt;

&lt;p&gt;Interesting thing happens on the next stage - when user needs to reach protected resources. Using previous approach we have to trigger authorization logic on every upcoming request on a different service which is &lt;code&gt;Auth&lt;/code&gt; in the example above. What happened when we employ JWT?&lt;/p&gt;

&lt;p&gt;Take a look at the diagram below.&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%2Fth4bbllayrjyqnul4ur4.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%2Fth4bbllayrjyqnul4ur4.png" alt="Figure 6. JWT based Authorization flow" width="800" height="387"&gt;&lt;/a&gt;Figure 6. JWT based Authorization flow&lt;/p&gt;

&lt;p&gt;When a user makes a request to access a protected resource, their client (typically the browser) automatically includes the JWT in the request headers. This is usually done by setting the Authorization header to "Bearer token", where &lt;code&gt;token&lt;/code&gt; is the actual JWT.&lt;/p&gt;

&lt;p&gt;Upon receiving the request, the &lt;code&gt;Service&lt;/code&gt; no longer needs to consult with the &lt;code&gt;Auth&lt;/code&gt; service for each incoming request. Instead, it can verify the JWT's integrity directly and decode the JWT on its own. This is possible because the JWT is cryptographically signed, and the &lt;code&gt;Service&lt;/code&gt; has access to the JWT secret key or public key (in case of asymmetric signing) used to sign the token by &lt;code&gt;Auth&lt;/code&gt; at the JWT creation time.&lt;/p&gt;

&lt;p&gt;Once the signature is verified, the &lt;code&gt;Service&lt;/code&gt; can decode the JWT payload. This payload contains all the necessary information about the user - their ID, roles, permissions, and any other relevant data that was included when the token was created. All of this happens without any database lookups or calls to the &lt;code&gt;Auth&lt;/code&gt; service.&lt;/p&gt;

&lt;p&gt;Finally with the user information available from the decoded JWT, the &lt;code&gt;Service&lt;/code&gt; can make immediate authorization decisions. It can check if the user has the necessary permissions to access the requested resource, all without any additional network calls or database queries.&lt;/p&gt;
&lt;h2&gt;
  
  
  JWT Structure
&lt;/h2&gt;

&lt;p&gt;To make this flow happen JWT should be able to carry on some information. That includes not only some useful information we may want to store in the token, but also data, allowing to make the flow secured.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc7519" rel="noopener noreferrer"&gt;RFC 7519&lt;/a&gt; defines the JWT as follows.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JSON Web Token (JWT) is a compact, URL-safe means of representing&lt;br&gt;
   claims to be transferred between two parties.  The claims in a JWT&lt;br&gt;
   are encoded as a JSON object that is used as the payload of a JSON&lt;br&gt;
   Web Signature (JWS) structure or as the plaintext of a JSON Web&lt;br&gt;
   Encryption (JWE) structure, enabling the claims to be digitally&lt;br&gt;
   signed or integrity protected with a Message Authentication Code&lt;br&gt;
   (MAC) and/or encrypted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Using that we can describe the structure of a JWT as of (usually) consisting of three parts: a header, a payload, and a signature, separated by &lt;code&gt;.&lt;/code&gt; (dot) symbol. The payload contains claims about the user, such as user ID, role, and expiration time. The signature, created using a secret key known only to the server, ensures the token's integrity and authenticity.&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%2Fuy8kplddispzjryuw04q.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%2Fuy8kplddispzjryuw04q.png" alt="Figure 7. JWT Structure" width="800" height="243"&gt;&lt;/a&gt;Figure 7. JWT Structure&lt;/p&gt;

&lt;p&gt;All these 3 parts are separated by dot symbol and following in the order of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;header . payload . signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These three components are encoded using the BASE64URL algorithm. This encoding method is closely related to standard BASE64, but with a few key differences. In BASE64URL, the output replaces the plus sign &lt;code&gt;+&lt;/code&gt; with a minus sign &lt;code&gt;-&lt;/code&gt;, and the forward slash &lt;code&gt;/&lt;/code&gt; becomes an underscore &lt;code&gt;_&lt;/code&gt;. Additionally, BASE64URL omits the typical padding found in standard BASE64, which usually consists of equal signs &lt;code&gt;=&lt;/code&gt; at the end of the encoded string. These modifications make the encoded output more suitable for use in URLs and other contexts where certain characters might cause issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Payload
&lt;/h3&gt;

&lt;p&gt;If the payload (middle) part is more or less clear, as it is the container for user defined data, it still worth to take a look at some predefined fields over there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc7519" rel="noopener noreferrer"&gt;RFC 7519&lt;/a&gt; standard calls data inside the payload a Climes Set and defines three classes of JWT Claim Names&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The JWT Claims Set represents a JSON object whose members are the&lt;br&gt;
   claims conveyed by the JWT.&lt;br&gt;&lt;br&gt;
   There are three classes of JWT Claim Names: Registered Claim Names,&lt;br&gt;
   Public Claim Names, and Private Claim Names.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Registered Climes are those which defined by the standard and used by applications in case they considered as mandatory by developers. Here they are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;jti&lt;/code&gt; (JWT ID) Claim
a case-sensitive unique identifier for the JWT, designed to prevent token replay and ensure uniqueness across multiple issuers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;iss&lt;/code&gt; (Issuer) Claim
identifies the JWT's issuer using a case-sensitive StringOrURI value, with processing typically application-specific&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;iat&lt;/code&gt; (Issued At) Claim
specifies the JWT's creation time, allowing for age determination of the token&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sub&lt;/code&gt; (Subject) Claim
identifies the JWT's principal using a case-sensitive StringOrURI value, which must be locally or globally unique, and typically serves as the subject of the token's claims&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aud&lt;/code&gt; (Audience) Claim
specifies the intended recipients of the JWT as a case-sensitive string or array of strings, each containing a StringOrURI value, and requires the processing principal to be identified within this claim for token acceptance&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;exp&lt;/code&gt; (Expiration Time) Claim
specifies the latest valid processing time for the JWT, with a small leeway often permitted for clock skew&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nbf&lt;/code&gt; (Not Before) Claim
sets the earliest time the JWT can be processed, with a small leeway often allowed for clock skew&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different applications may use different claims from the list above, but many of them rely at least on 2 - which are &lt;code&gt;iat&lt;/code&gt; and &lt;code&gt;exp&lt;/code&gt; to determine the lifecycle of the token itself and when it should be regenerated.&lt;/p&gt;

&lt;p&gt;Public Claims contains user defined information and may include there everything in JSON format. Usually developers create it with user data, which is necessary to be shown on the UI, user permissions and some additional information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Header
&lt;/h3&gt;

&lt;p&gt;The JWT header plays a critical role in the token's functionality. This component,  appearing at the beginning of the token, contains metadata about the token itself. Two key elements stored in the header are the token type and the hashing algorithm used for the signature.&lt;/p&gt;

&lt;p&gt;The token type, denoted by the &lt;code&gt;typ&lt;/code&gt; claim, usually specifies &lt;code&gt;JWT&lt;/code&gt; to indicate that the token is indeed a JSON Web Token. This information helps systems quickly identify and process the token appropriately. The hashing algorithm, represented by the &lt;code&gt;alg&lt;/code&gt; claim, specifies the cryptographic algorithm employed to create the token's signature. Common algorithms are HMAC SHA256 (HS256) or RSA SHA256 (RS256), but there are many others which are possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Signature
&lt;/h3&gt;

&lt;p&gt;This section of the JWT is supposed to provide a mechanism of verification of the token integrity and authenticity. It is important and serves only for a one reason - prevent modification of any information at any stage.&lt;/p&gt;

&lt;p&gt;This validation of the JWT happen on the backend &lt;code&gt;Service&lt;/code&gt;, right after the &lt;code&gt;Service&lt;/code&gt; received user's request for data. Server actually can recreate the signature using the same secret key (which is secretly stored on the backend) and algorithm (which is defined in the JWT header section). If the newly generated signature matches the one in the token, it confirms that the contents haven't been altered since the token's creation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refreshing JSON Web Tokens
&lt;/h2&gt;

&lt;p&gt;There is a last but not least thing to consider - JWT lifetime. After issuing a JWT to a user, we actually allowing him to access the protected data in accordance with his level of permissions. However do we want to limit it in time? In other words - do we want that user confirms his authenticity after some time or not?&lt;/p&gt;

&lt;p&gt;Even though this question is not that obvious, the answer is straightforward and positive. User must to re-confirms his authenticity and he should do that within a reasonable amount of time. It matters because we can't exclude the situation or JWT leakage, as what goes to browser is available to the rest of the world. JWT provides safety mechanism from data modifications (by signing them and later signature verification), but they do not save from reusing them.&lt;/p&gt;

&lt;p&gt;This brings us to the concept of JWT refreshing. While limiting the lifetime of a JWT is necessary for security reasons, it can also lead to a poor user experience if the token expires too quickly, forcing frequent re-authentication. And this is where refresh tokens come into play.&lt;/p&gt;

&lt;p&gt;A refresh token is another special token that can be used to obtain a new JWT once the original JWT has expired. Unlike JWTs, refresh tokens are typically stored server-side only and are associated with a specific user, using some of its unique data, like a hash of the JWT itself. They have a longer lifespan than JWTs but are only used to verify one thing - the old JWT is valid even if it is expired and we can safely issue a new one. In other words it let us know that original JWT was not modified and still can be trusted.&lt;/p&gt;

&lt;p&gt;This approach balances security and user experience. It allows for short-lived JWT reducing the window of potential token misuse, while also allowing users to remain logged in for extended periods without explicit re-authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scalability Advantages of JWT
&lt;/h2&gt;

&lt;p&gt;The stateless nature of JWT provides a significant boost especially for distributed systems in terms of scalability, when compared to traditional session-based authentication systems. This makes JWT an attractive approach for modern, distributed applications, such as those deployed in cloud environments or utilizing load-balanced architectures.&lt;/p&gt;

&lt;p&gt;In traditional session-based systems, user authentication data is stored on the server side, typically in a database or in-memory store. Each time a user makes a request, the server must retrieve this session information to verify the user's identity and permissions. As the number of concurrent users grows, this approach can lead to increased load on the session store, potentially becoming a bottleneck in the system.&lt;/p&gt;

&lt;p&gt;JWT, on the other hand, encapsulate all necessary authentication and authorization information within the token itself. When a user authenticates, the server generates a JWT containing the user's identity and permissions, then sends this token back to the client. For subsequent requests, the client includes this token, allowing the server to verify the user's identity and permissions without querying a central session store.&lt;/p&gt;

&lt;p&gt;JWT's stateless nature aligns well with microservices architectures. In a microservices environment, different services can easily validate the JWT and extract necessary information without maintaining complex, centralized session management systems. This decoupling of authentication state from individual services enhances the overall scalability and flexibility of the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;In the next article, we'll take a look further into the authentication and authorization cased by integrating JWT and two other important protocols: OAuth 2.0 and OpenID Connect (OIDC). Stay tuned to learn how combining JWT with OAuth 2.0 and OIDC can enhance your application's security posture while maintaining the scalability benefits we've discussed!&lt;/p&gt;

</description>
      <category>jwt</category>
      <category>security</category>
      <category>cloud</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Using Domain-Driven Design to to Create Microservice App</title>
      <dc:creator>Eugene Zimin</dc:creator>
      <pubDate>Mon, 24 Jun 2024 07:18:01 +0000</pubDate>
      <link>https://dev.to/eugene-zimin/leveraging-domain-driven-design-for-application-design-58e2</link>
      <guid>https://dev.to/eugene-zimin/leveraging-domain-driven-design-for-application-design-58e2</guid>
      <description>&lt;p&gt;Creating scalable and maintainable applications remains a constant challenge in the software development industry. As digital ecosystems grow more complex and user demands evolve rapidly, developers face increasing pressure to build systems that can adapt and expand seamlessly.&lt;/p&gt;

&lt;p&gt;Scalability presents a multifaceted challenge. Applications must handle growing user bases, increasing data volumes, and expanding feature sets without compromising performance. This requires careful architectural decisions, efficient resource utilization, and the ability to distribute workloads effectively across multiple servers or cloud instances.&lt;/p&gt;

&lt;p&gt;Maintainability, on the other hand, focuses on the long-term health of the codebase. As applications grow in complexity, keeping the code clean, understandable, and easily modifiable becomes crucial. This involves creating modular designs, adhering to coding standards, and implementing robust testing strategies. A maintainable codebase allows for faster bug fixes, easier feature additions, and smoother onboarding of new team members.&lt;/p&gt;

&lt;p&gt;The intersection of scalability and maintainability often leads to trade-offs. Highly optimized systems for scalability may sacrifice code readability, while overly modular designs for maintainability might introduce performance overhead. Striking the right balance requires deep domain knowledge, technical expertise, and a forward-thinking approach to software design.&lt;/p&gt;

&lt;p&gt;The rapid pace of technological change adds another layer of complexity. Developers must create systems that not only meet current needs but also remain flexible enough to incorporate future technologies and methodologies. This forward-looking approach is essential in preventing technical debt and ensuring the longevity of the application.&lt;/p&gt;

&lt;p&gt;Rapid pace of technological change adds another layer of complexity. Developers must create systems that not only meet current needs but also remain flexible enough to incorporate future technologies and methodologies. This forward-looking approach is essential in preventing technical debt and ensuring the longevity of the application.&lt;/p&gt;

&lt;p&gt;Domain-Driven Design, first introduced by Eric Evans, emphasizes aligning software design with business needs. It provides a set of patterns and practices that help architects and developers create flexible, modular systems that can evolve with changing requirements. By centering the design process around the core domain and domain logic, DDD facilitates the creation of software that truly reflects the underlying business model.&lt;/p&gt;

&lt;p&gt;Domain-Driven Design provides a framework for creating systems that are both scalable in terms of functionality and maintainable in terms of code organization. It offers strategies for managing complexity, facilitating communication between technical and non-technical stakeholders, and creating flexible systems that can evolve with changing business needs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Domain-Driven Design Quick Overview
&lt;/h1&gt;

&lt;p&gt;Domain-Driven Design (DDD) is a software development approach that places the project's core focus on the domain and domain logic. Introduced by Eric Evans in his seminal book, DDD provides a set of principles and patterns for creating complex software systems that closely mirror the business domain they serve.&lt;/p&gt;

&lt;p&gt;At the heart of DDD lies the concept of the &lt;strong&gt;ubiquitous language&lt;/strong&gt;. This shared vocabulary, meticulously crafted by developers in collaboration with domain experts, forms the foundation of the model. It ensures that all stakeholders, from business analysts to programmers, communicate effectively using terms and concepts directly related to the domain. This alignment significantly reduces misunderstandings and helps create software that truly reflects business needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bounded contexts&lt;/strong&gt; represent another cornerstone of DDD. These are explicit boundaries within which a particular domain model applies. In large systems, different parts may have different domain models, each with its own ubiquitous language. Recognizing and defining these boundaries helps manage complexity and allows teams to work independently on different parts of the system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aggregates&lt;/strong&gt; are a key tactical pattern in DDD. An aggregate is a cluster of domain objects treated as a single unit for data changes. The aggregate root, a single entity within the aggregate, ensures the consistency of changes within the aggregate and controls access to its members. This pattern is particularly useful in defining clear boundaries for transactions and maintaining data integrity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Domain events&lt;/strong&gt; are another crucial concept in DDD. These represent significant occurrences within the domain. By modeling important changes as events, DDD facilitates loose coupling between different parts of the system, enabling more flexible and scalable architectures.&lt;/p&gt;

&lt;p&gt;In the context of microservices architecture, DDD proves exceptionally valuable. The bounded contexts in DDD often align well with individual microservices, providing a natural way to decompose a complex system into manageable, independently deployable services. This alignment helps in creating a modular system where each service has a clear responsibility and a well-defined interface.&lt;/p&gt;

&lt;p&gt;The strategic patterns of DDD, such as &lt;strong&gt;context mapping&lt;/strong&gt;, help in understanding and defining the relationships between different bounded contexts or microservices. This is crucial for managing the complexity that arises from distributed systems and ensuring that the overall system remains coherent.&lt;/p&gt;

&lt;p&gt;By applying DDD principles, developers can create software that's not only aligned with business needs but also inherently more maintainable and scalable. The focus on the core domain ensures that development efforts are concentrated where they provide the most value. The clear boundaries and well-defined interfaces facilitate easier changes and extensions to the system over time.&lt;/p&gt;

&lt;p&gt;In the following sections, we will explore how these DDD concepts can be applied to create a Twitter-like application using a two-microservice architecture. This practical example will demonstrate how DDD can guide the design of complex systems, resulting in a flexible and maintainable solution.&lt;/p&gt;

&lt;h1&gt;
  
  
  Defining the Bounded Contexts
&lt;/h1&gt;

&lt;p&gt;In applying Domain-Driven Design (DDD) to our Twitter-like application, the first step is to identify and define the bounded contexts. Bounded contexts are crucial in DDD as they delineate the boundaries within which a particular domain model applies. For our simplified Twitter-like platform, we'll focus on two primary bounded contexts, each corresponding to a microservice: &lt;code&gt;UserManagementService&lt;/code&gt; and &lt;code&gt;MessagingService&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  UserManagementService Bounded Context
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;UserManagementService&lt;/code&gt; encompasses all aspects related to user accounts and profiles. This context is responsible for managing user identities, authentication, and user-specific information. Within this bounded context, the core concepts include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User&lt;/strong&gt; - the central entity representing an individual account on the platform, which contains user-specific details such as display name, bio, and profile picture, authentication information like username and password, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role&lt;/strong&gt; - defines user role in the whole system, whether he's a messages producer or messages consumer only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;ubiquitous language&lt;/strong&gt; within this context includes terms like "register", "login", "logout", "authentication" and "authorization". The &lt;code&gt;UserManagementService&lt;/code&gt; operates independently, focusing solely on user-related operations without direct concern for messaging or content creation.&lt;/p&gt;

&lt;h2&gt;
  
  
  MessagingService Bounded Context
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;MessagingService&lt;/code&gt; handles all aspects of content creation, distribution, and consumption. This context is more complex as it manages both the creation of messages (tweets) and the subscription system. Key concepts in this bounded context include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Message&lt;/strong&gt;: The core entity representing a piece of content (similar to a tweet).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscription&lt;/strong&gt;: Represents the follow relationship between users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feed&lt;/strong&gt;: A collection of messages from subscribed users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Producer&lt;/strong&gt;: A user who creates content (messages).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consumer&lt;/strong&gt;: A user who consumes content from their subscriptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ubiquitous language here includes terms like "post message", "subscribe", "unsubscribe", "consume message". This context deals with the dynamic aspects of the platform, managing the flow of content between users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context Interactions
&lt;/h2&gt;

&lt;p&gt;While these bounded contexts are separate, they do interact. The &lt;code&gt;MessagingService&lt;/code&gt; needs to reference users, but it does so without delving into the internal complexities of user management. Instead, it might use a simplified representation of a user, containing only the necessary information for messaging purposes.&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%2Fbm3m0vfz5zt36z3asd99.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%2Fbm3m0vfz5zt36z3asd99.png" alt="Context Interactions" width="800" height="437"&gt;&lt;/a&gt;Figure 1. Context Interactions between bounded contexts&lt;/p&gt;

&lt;p&gt;For instance, when a user posts a message, the &lt;code&gt;MessagingService&lt;/code&gt; doesn't need to know about the user's password or email address. It only needs a user identifier and perhaps a display name. This separation allows each context to evolve independently while maintaining necessary connections.&lt;/p&gt;

&lt;p&gt;This separation is further reinforced by implementing the database-per-service pattern. In this approach, each microservice maintains its own dedicated database. The &lt;code&gt;UserManagementService&lt;/code&gt; has a database that stores comprehensive user information, including sensitive data like passwords and email addresses. On the other hand, the &lt;code&gt;MessagingService&lt;/code&gt; database focuses on message content, user subscriptions, and a minimal set of user data required for its operations.&lt;/p&gt;

&lt;p&gt;To maintain necessary connections between services, the &lt;code&gt;MessagingService&lt;/code&gt; keeps a minimal replica of user data required for its operations. This typically includes the user ID only. If &lt;code&gt;MessagingService&lt;/code&gt; requires some data related to the user, it may request that information from &lt;code&gt;UserManagementService&lt;/code&gt;. This may be useful for checking user roles for example or user name to display.&lt;/p&gt;

&lt;p&gt;Technically speaking, this approach introduces some data redundancy, however organized correctly, such a redundancy may be reviewed as nothing much but as foreign keys used between different tables in RDBMS.&lt;/p&gt;

&lt;p&gt;It allows each service to operate independently most of the time, enhancing overall system resilience. It also aligns well with the DDD principle of respecting bounded context boundaries, as each service has full control over its own data model and storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context Mapping
&lt;/h2&gt;

&lt;p&gt;To manage the relationship between these bounded contexts, we employ context mapping. In this case, we might use a Customer-Supplier relationship, where the &lt;code&gt;UserManagementService&lt;/code&gt; (supplier) provides necessary user data to the &lt;code&gt;MessagingService&lt;/code&gt; (customer). This could be implemented through API calls or message queues, ensuring that each service has the information it needs without tightly coupling the two contexts.&lt;/p&gt;

&lt;p&gt;Implementation of this context mapping involves several key aspects. First, the &lt;code&gt;UserManagementService&lt;/code&gt; exposes a well-defined API that the &lt;code&gt;MessagingService&lt;/code&gt; can use to retrieve essential user information. This API is designed to provide only the necessary data, such as user IDs and user roles, without exposing sensitive information. For example, when a new message is posted, the &lt;code&gt;MessagingService&lt;/code&gt; might make an API call to the &lt;code&gt;UserManagementService&lt;/code&gt; to verify the user's existence and his role.&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%2Flh3a5qlwnltsdi37q8hk.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%2Flh3a5qlwnltsdi37q8hk.png" alt="Context Mapping" width="800" height="437"&gt;&lt;/a&gt;Figure 2. Context Mapping between different bounded contexts&lt;/p&gt;

&lt;p&gt;As part of the Customer-Supplier relationship, we define clear Service Level Agreements (SLAs) between the services. These SLAs specify the expected request and response structure, timeline for API calls, the frequency of event publications, and the handling of service outages.&lt;/p&gt;

&lt;p&gt;Leveraging these patterns we will create a foundation for a modular, maintainable system. Each context has a clear responsibility and a well-defined interface, allowing for independent development and scaling. This separation also provides flexibility for future expansions, such as adding new features or integrating with external systems.&lt;/p&gt;

&lt;p&gt;In the following sections, we'll delve deeper into each of these bounded contexts, exploring their domain models, aggregates, and services in detail.&lt;/p&gt;

&lt;h1&gt;
  
  
  User Management Service
&lt;/h1&gt;

&lt;p&gt;The &lt;code&gt;UserManagementService&lt;/code&gt; forms a crucial part of our Messaging application, handling all aspects related to user accounts and roles. This service embodies its own bounded context, focusing solely on user-related operations. Let's delve into the key components of this service, structured according to Domain-Driven Design principles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Domain Model
&lt;/h2&gt;

&lt;p&gt;At the core of the &lt;code&gt;UserManagementService&lt;/code&gt; lies the User aggregate. In DDD, an aggregate is a cluster of domain objects treated as a single unit. The User aggregate encapsulates all user-related data and behavior.&lt;/p&gt;

&lt;p&gt;The User aggregate root contains essential attributes such as &lt;code&gt;userId&lt;/code&gt;, &lt;code&gt;username&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, and &lt;code&gt;password&lt;/code&gt;. It also includes methods for user authentication, user roles and user authorization. By centralizing these responsibilities within the User aggregate, we ensure that all user-related operations maintain data consistency and adhere to business rules.&lt;/p&gt;

&lt;p&gt;Associated with the User aggregate are several value objects. These are immutable objects that describe characteristics of the User but have no conceptual identity. Examples include Email and Password. These value objects encapsulate validation logic, ensuring that email addresses are properly formatted in accordance with &lt;a href="https://datatracker.ietf.org/doc/html/rfc2822#section-3.4"&gt;RFC 2822&lt;/a&gt; and passwords meet security requirements by its length and other criteria.&lt;/p&gt;

&lt;p&gt;The Profile value object represents user-specific details like displayName, bio, and profilePictureUrl. By modelling Profile as a separate value object, we allow for easy expansion of profile-related features without cluttering the User aggregate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;UserRepository&lt;/code&gt; interface defines methods for persisting and retrieving User aggregates. This abstraction allows us to separate the domain model from the underlying data storage mechanism. The implementation of this repository interfaces with our chosen database system, in this case, MySQL.&lt;/p&gt;

&lt;p&gt;The repository includes methods such as findById, findByUsername, and regular CRUD operations (CREATE-READ-UPDATE-DELETE). It also provides more specific query methods like findByEmail, which might be used during the user registration process to ensure email uniqueness.&lt;/p&gt;

&lt;p&gt;Data model, which is also scoped to User aggregation only, might be represented as on the figure below.&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%2F7gsgsnvi5ud053y1adkm.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%2F7gsgsnvi5ud053y1adkm.png" alt="UMS Data Model" width="800" height="375"&gt;&lt;/a&gt;Figure 3. User Management Data Model&lt;/p&gt;

&lt;h2&gt;
  
  
  Domain Services (Domain Flows)
&lt;/h2&gt;

&lt;p&gt;While most business logic resides within the User aggregate, some operations involve multiple aggregates or require external resources. For these, we use domain services which may exist as separate services or might be defined as modules inside a single service. As of now we will prefer to define those operations as separate reusable modules inside one service.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;UserRegistrationModule&lt;/code&gt; handles the process of user registration, which might involve checking for existing users, validating input, and triggering welcome emails. This module coordinates between the User aggregate, UserRepository, and potentially external services like email providers.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;AuthModule&lt;/code&gt; manages user login processes, including password verification and generation of authentication tokens, as well as handles requests to identify user roles. This module works closely with the User aggregate but keeps the authentication and authorization logic separate, allowing for easier updates to authentication and authorization mechanisms in the future.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ApplicationGatewayModule&lt;/code&gt; is responsible for communication with other external components and services. In some other sources it is also called a Controller, however we try to include here only logic, responsible for terminating incoming traffic, data serialization, input validation and forming responses to return them to the requesters. This module acts like a communication facade between different external services and internal modules, providing SLA for them, and at the same time keeping flexibility of  internal modules.&lt;/p&gt;

&lt;p&gt;The last module we may want to use is &lt;code&gt;DataAccessLayerModule&lt;/code&gt;. We already defined the data model, now we need to determine the way to communicate with it. Using direct calls to the database from any point of the code is possible but may not be considered as a good idea because of many reasons, main ones are correlated to the high coupling and low cohesion between data model and application logic. Using this module we may decompose and abstract access our logic from accessing data. Instead of creating a specific SQL query to select specific user and access his permissions, we may simply create a method, called &lt;code&gt;getUserWithRoles&lt;/code&gt;, and hide the implementation behind it. In that case we reach high consistency by calling the only one method, which we will be a single point to access data.&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%2Fq8cc29z5cg11vtz4hqw7.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%2Fq8cc29z5cg11vtz4hqw7.png" alt="Domain Flow" width="800" height="209"&gt;&lt;/a&gt;Figure 4. Block Diagram of Domain Modules&lt;/p&gt;

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

&lt;p&gt;While not strictly part of the domain model, the &lt;code&gt;UserManagementService&lt;/code&gt; also addresses several infrastructure concerns. These include implementing security measures like password hashing and token-based authentication, setting up database connections and ORM mappings, and configuring API endpoints.&lt;/p&gt;

&lt;p&gt;The service also implements event publishing for significant domain events. When a user updates their profile, for instance, the service publishes a UserProfileUpdated event. Other services, like our further considering &lt;code&gt;NotificationService&lt;/code&gt;, can subscribe to these events to maintain consistency across the system and deliver different state changes.&lt;/p&gt;

&lt;p&gt;By structuring the &lt;code&gt;UserManagementService&lt;/code&gt; according to DDD principles, we create a robust, maintainable service that clearly encapsulates all user-related functionality. This design allows for easy extension of user management features and provides a solid foundation for our Messaging application.&lt;/p&gt;

&lt;h1&gt;
  
  
  MessagingService
&lt;/h1&gt;

&lt;p&gt;The MessagingService forms the core of our Messaging application's content creation and distribution system. This service encompasses its own bounded context, managing messages (tweets) and subscriptions. Let's explore the key components of this service, structured according to Domain-Driven Design principles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Domain Model
&lt;/h2&gt;

&lt;p&gt;The central aggregate in the &lt;code&gt;MessagingService&lt;/code&gt; is the Message. This aggregate encapsulates the content created by users, along with metadata such as creation time and author information. The Message aggregate root contains attributes like &lt;code&gt;messageID&lt;/code&gt;, &lt;code&gt;content&lt;/code&gt;, &lt;code&gt;authorID&lt;/code&gt; (which is a &lt;code&gt;userID&lt;/code&gt;), and &lt;code&gt;createdAt&lt;/code&gt;. It also includes methods for creating, editing (if allowed), and deleting messages.&lt;/p&gt;

&lt;p&gt;Another crucial aggregate is the &lt;code&gt;Subscription&lt;/code&gt;, representing the follow relationship between users. The Subscription aggregate contains a &lt;code&gt;producerID&lt;/code&gt; and a &lt;code&gt;subscriberID&lt;/code&gt;, which are no more than &lt;code&gt;userID&lt;/code&gt; correlated to the appropriate data from the &lt;code&gt;UserManagementService&lt;/code&gt;, along with subscription status and creation date. This aggregate is responsible for managing the relationships between content producers and consumers.&lt;/p&gt;

&lt;p&gt;Value objects in this context might include &lt;code&gt;MessageContent&lt;/code&gt;, which encapsulates the actual text or media content of a message and its author ID, to establish and determine the subscription status.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;MessagingService&lt;/code&gt; might include several repositories in case we would like to split it into standalone services to manage messages and subscriptions. In this article we consider that functionality as closely related and manage it under the single umbrella of the &lt;code&gt;MesagingService&lt;/code&gt; and within the single database for this service. &lt;/p&gt;

&lt;p&gt;The MessageRepository handles persistence and retrieval of Message aggregates. It includes methods like regular CRUD, &lt;code&gt;findByMessageId&lt;/code&gt;, &lt;code&gt;findByAuthorId&lt;/code&gt;, and &lt;code&gt;findBySubscriberId&lt;/code&gt;. We may also implement another method allowing users to filter messages by the time when they were created, which may be another convenient use case, and call this method as &lt;code&gt;findByCreatedAt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As it is seen, this database includes all those entities related to the messages and subscriptions. It's schema might be represented like on the figure below.&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%2Fjz7i2fy1p7giil23axrh.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%2Fjz7i2fy1p7giil23axrh.png" alt="Messaging Model" width="800" height="376"&gt;&lt;/a&gt;Figure 5. Messaging Service Data Model&lt;/p&gt;

&lt;h2&gt;
  
  
  Domain Services (Domain Flows)
&lt;/h2&gt;

&lt;p&gt;In this service we may define the same amount of modules as in the &lt;code&gt;UserManagementService&lt;/code&gt; - 4, where 2 of them would be identical - &lt;code&gt;DataAccessLayerModule&lt;/code&gt; and &lt;code&gt;ApplicationGatewayModule&lt;/code&gt;, as these modules' responsibilities are generic and service agnostic. These 2 modules are intended provide border modules for data communication between &lt;code&gt;MessagingService&lt;/code&gt; other ones. Eventually their goal is to convert and adjust incoming or outgoing data with its internal representation happening inside this Domain.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MessagingModule&lt;/code&gt; in this case this is the core module for the whole service as it manages messages creation, retrieval and editing (if supported). This modules accept data from different sources, including user input, like message's text, user data, obtained from &lt;code&gt;UserManagementService&lt;/code&gt; by using User ID, provided by user and using internal logic performs the requested operation - create, retrieve or edit (if supported) message.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;SubscriptionModule&lt;/code&gt; handles the complex logic of managing user subscriptions, including creating new subscriptions, handling unsubscribes, and managing subscription statuses like muting or blocking.&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%2Ffvqxv8n0chiynx01jjb7.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%2Ffvqxv8n0chiynx01jjb7.png" alt="Domain Flows" width="800" height="208"&gt;&lt;/a&gt;Figure 6. Block Diagram of Domain Modules&lt;/p&gt;

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

&lt;p&gt;The &lt;code&gt;MessagingService&lt;/code&gt; implements several infrastructure-level features to support its operations, as concerns for this service should be different than for the &lt;code&gt;UserManagementService&lt;/code&gt;. These include setting up message queues for handling high volumes of new messages, implementing caching mechanisms for frequently accessed subscriptions, and managing database connections for efficient data retrieval.&lt;/p&gt;

&lt;p&gt;The service also may publish domain events such as &lt;code&gt;MessageCreated&lt;/code&gt; and &lt;code&gt;SubscriptionChanged&lt;/code&gt; to be consumed by other parts of the system or external services for features like notifications or analytics.&lt;/p&gt;

&lt;p&gt;By structuring the MessagingService according to DDD principles, we create a robust and scalable system for handling the core functionality of our Messaging application. This design allows for efficient management of messages, subscriptions, while providing clear boundaries for future extensions and optimizations.&lt;/p&gt;

&lt;h1&gt;
  
  
  Scalability and Performance Considerations
&lt;/h1&gt;

&lt;p&gt;As our application grows in popularity, scalability and performance become critical concerns. This section explores strategies to ensure our microservices architecture can handle increasing loads while maintaining responsiveness and reliability. Generally speaking those techniques below are not critical for application functionality, however they become those when the system starts experiencing the high load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Horizontal Scaling
&lt;/h2&gt;

&lt;p&gt;Both the &lt;code&gt;UserManagementService&lt;/code&gt; and &lt;code&gt;MessagingService&lt;/code&gt; are designed to be stateless, allowing for easy horizontal scaling. As user traffic increases, we can deploy multiple instances of each service behind a load balancer. This approach distributes the load across multiple servers, improving overall system capacity and resilience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Scaling
&lt;/h2&gt;

&lt;p&gt;As the volume of data grows, database performance can become a bottleneck. To address this, we may implement several strategies for that.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;UserManagementService&lt;/code&gt;, we establish a system of read replicas, as this service load assumes prevail retrieval operations over writing ones. This setup allows us to distribute read operations across multiple database instances, significantly reducing the load on the primary database. By directing read-heavy operations to these replicas, we ensure that the primary database can focus on write operations, thereby improving overall system performance and responsiveness.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;MessagingService&lt;/code&gt;, which deals with a substantially larger volume of data, requires a more robust scaling solution. Here, we may implement database sharding, a technique that involves partitioning data across multiple database instances based on user ID or message ID ranges. This approach not only distributes the data itself but also spreads the query load across multiple servers. As a result, we can handle a much larger number of concurrent operations and store a greater volume of data than would be possible with a single database instance.&lt;/p&gt;

&lt;p&gt;In addition to these structural changes, we should place a strong emphasis on query optimization and indexing. Our database administrators and developers work in tandem to continuously monitor query performance, identifying frequently used query patterns and ensuring that appropriate indexes are in place to support them. This ongoing process of refinement helps maintain database efficiency even as the volume and complexity of data grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching Strategies
&lt;/h2&gt;

&lt;p&gt;Caching plays a pivotal role in performance optimization strategy, serving as a crucial layer between our services and the database. In the &lt;code&gt;UserManagementService&lt;/code&gt;, we implement a sophisticated caching mechanism for user profile data. We may utilize any in-memory storage, like Redis, for high-performance in-memory data store, where we may cache frequently accessed user information. This approach significantly reduces the number of database queries required for common operations, such as retrieving user details for display or authentication purposes.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;MessagingService&lt;/code&gt; employs a more complex, multi-tiered caching strategy to handle the high-volume, real-time nature of social media feeds. We may use a combination of in-memory caching with Caffeine for the most recent and frequently accessed feed entries, providing near-instantaneous access to this hot data. For older or less frequently accessed feed data, we leverage Redis as a second-level cache. This tiered approach allows us to balance the need for ultra-fast access to recent content with the ability to efficiently store and retrieve a larger volume of historical data.&lt;/p&gt;

&lt;p&gt;As our system scales horizontally, maintaining cache consistency across multiple service instances becomes critical. To address this, we configure Redis in cluster mode, ensuring that our caching layer is both distributed and consistent. This setup allows us to scale our caching infrastructure in tandem with our application services, maintaining performance as user numbers grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Eventual Consistency and CAP Theorem Considerations
&lt;/h2&gt;

&lt;p&gt;In designing our distributed system, we've had to grapple with the fundamental trade-offs described by the CAP theorem, which states that in the event of a network partition, a distributed system must choose between consistency and availability. Given the nature of our application, where the immediacy of information flow is a key feature, we've opted to prioritize availability and partition tolerance over strict consistency in many scenarios.&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%2F6p7o1a3fwodl3k74f9gv.jpg" 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%2F6p7o1a3fwodl3k74f9gv.jpg" alt="CAP" width="632" height="462"&gt;&lt;/a&gt;Figure 7. CAP Theorem Problem&lt;/p&gt;

&lt;p&gt;This decision manifests in our embrace of eventual consistency. For instance, when a user updates their profile information in the &lt;code&gt;UserManagementService&lt;/code&gt;, we acknowledge and apply this change immediately in that service. However, we accept that there may be a short delay before this update is reflected in the &lt;code&gt;MessagingService&lt;/code&gt; or in cached data across the system. During this brief period, different parts of the system may have slightly different views of the user's data.&lt;/p&gt;

&lt;p&gt;While this approach introduces the possibility of temporary inconsistencies, it allows our system to remain highly available and responsive, even in the face of network issues or high load. We mitigate the impact of these inconsistencies through careful system design, such as including timestamps with data updates to help resolve conflicts, and by setting appropriate user expectations about the speed of data propagation.&lt;/p&gt;

&lt;p&gt;This eventual consistency model extends to other areas of our system as well, such as follower counts or message distribution. By relaxing the requirement for immediate, system-wide consistency, we can process high volumes of operations more efficiently, leading to a more scalable and responsive system overall. However, we're also careful to identify those areas of our application where strong consistency is necessary, such as in financial transactions or security-critical operations, and design those components accordingly.&lt;/p&gt;

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

&lt;p&gt;The journey of creating a Twitter-like application using Domain-Driven Design (DDD) and microservices architecture offers valuable insights into building complex, scalable systems. Through our exploration of the &lt;code&gt;UserManagementService&lt;/code&gt; and &lt;code&gt;MessagingService&lt;/code&gt;, we've demonstrated how DDD principles can guide the development of a robust, maintainable, and flexible application architecture.&lt;/p&gt;

&lt;p&gt;By focusing on clearly defined bounded contexts, we've created services that are independently deployable and scalable, yet cohesive in their overall functionality. The use of aggregates, repositories, and domain services has allowed us to encapsulate complex business logic within each service, promoting code that is both more understandable and more closely aligned with the underlying business domain. &lt;/p&gt;

&lt;p&gt;Our approach to data management, including the implementation of the database-per-service pattern and sophisticated caching strategies, showcases how modern distributed systems can handle large volumes of data while maintaining performance and responsiveness. The adoption of event-driven architecture for inter-service communication highlights the power of loose coupling in creating systems that are both resilient and adaptable to change.&lt;/p&gt;

&lt;p&gt;Throughout this process, we've had to make important architectural decisions, balancing concerns such as data consistency, scalability, and system complexity. Our choice to embrace eventual consistency in certain areas of the application demonstrates the nuanced thinking required when designing distributed systems, taking into account the trade-offs described by the CAP theorem.&lt;/p&gt;

&lt;p&gt;While our implementation focuses on core functionalities, omitting features like notifications and commenting, the architecture we've designed provides a solid foundation for future expansion. The principles and patterns we've applied can be extended to accommodate new features and services as the application grows.&lt;/p&gt;

&lt;p&gt;It's important to note that the journey doesn't end with implementation. Continuous monitoring, performance optimization, and iterative improvement are crucial for maintaining and evolving such a system. As user behavior changes and new technologies emerge, our Twitter-like application must be ready to adapt and scale accordingly.&lt;/p&gt;

&lt;p&gt;The combination of Domain-Driven Design and microservices architecture offers a powerful approach to building complex, scalable applications. By focusing on clear domain boundaries, embracing eventual consistency where appropriate, and leveraging modern technologies for data management and inter-service communication, we can create systems that are not only capable of handling current demands but are also well-positioned to evolve with future needs.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>architecture</category>
      <category>microservices</category>
      <category>eventdriven</category>
    </item>
  </channel>
</rss>
