<?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: D Abhiram Karanth</title>
    <description>The latest articles on DEV Community by D Abhiram Karanth (@abhiram_karanth).</description>
    <link>https://dev.to/abhiram_karanth</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%2F3743977%2Ff9095d74-9ae1-40b5-a941-6f67ba009987.jpeg</url>
      <title>DEV Community: D Abhiram Karanth</title>
      <link>https://dev.to/abhiram_karanth</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abhiram_karanth"/>
    <language>en</language>
    <item>
      <title>Stop Copy-Pasting Auth Logic. Build It Once.</title>
      <dc:creator>D Abhiram Karanth</dc:creator>
      <pubDate>Sun, 01 Feb 2026 09:25:25 +0000</pubDate>
      <link>https://dev.to/abhiram_karanth/stop-copy-pasting-auth-logic-build-it-once-209n</link>
      <guid>https://dev.to/abhiram_karanth/stop-copy-pasting-auth-logic-build-it-once-209n</guid>
      <description>&lt;p&gt;Authentication is one of those things every dev team rebuilds from scratch. Every. Single. Time.&lt;/p&gt;

&lt;p&gt;App A has login? Cool. App B copies it. App C "improves" it. Fast forward six months and you've got:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Three different token formats&lt;/li&gt;
&lt;li&gt;Two apps that don't actually log users out&lt;/li&gt;
&lt;li&gt;One app that expires tokens after 15 minutes for "security"&lt;/li&gt;
&lt;li&gt;Zero chance of sanity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;There's a better way.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;Auth-Flow&lt;/strong&gt; — a centralized authentication service in Go that you build once and reuse everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/abhiram-karanth-core/auth-flow" rel="noopener noreferrer"&gt;https://github.com/abhiram-karanth-core/auth-flow&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Scattered Auth
&lt;/h2&gt;

&lt;p&gt;Here's how most teams handle authentication:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy auth code from the last project&lt;/li&gt;
&lt;li&gt;Tweak it slightly for "this app's needs"&lt;/li&gt;
&lt;li&gt;Ship it&lt;/li&gt;
&lt;li&gt;Repeat for the next service&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This works... until it doesn't.&lt;/p&gt;

&lt;p&gt;Eventually you hit the wall:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can't revoke tokens across services&lt;/li&gt;
&lt;li&gt;Logout doesn't actually work&lt;/li&gt;
&lt;li&gt;Every service has different expiration rules&lt;/li&gt;
&lt;li&gt;Nobody remembers which app stores passwords how&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Auth shouldn't be copypasta. It should be infrastructure.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Auth-Flow Does
&lt;/h2&gt;

&lt;p&gt;Auth-Flow is a standalone service that handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OAuth 2.0 (starting with Google, easily extensible)&lt;/li&gt;
&lt;li&gt;JWT minting with proper claims&lt;/li&gt;
&lt;li&gt;Real logout (yes, actually works)&lt;/li&gt;
&lt;li&gt;Token revocation via Redis&lt;/li&gt;
&lt;li&gt;Stateless validation for your other services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your apps don't handle auth anymore. They just &lt;strong&gt;trust&lt;/strong&gt; this service.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Auth-Flow supports &lt;strong&gt;two authentication paths&lt;/strong&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: OAuth Flow (Google)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;User clicks "Login with Google"&lt;/li&gt;
&lt;li&gt;Auth-Flow redirects to Google&lt;/li&gt;
&lt;li&gt;Google sends back an auth code&lt;/li&gt;
&lt;li&gt;Auth-Flow exchanges it for user info&lt;/li&gt;
&lt;li&gt;Auth-Flow mints a signed JWT&lt;/li&gt;
&lt;li&gt;Client gets the token&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Option 2: Custom Authentication (&lt;code&gt;/mint&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Already verified a user through your own system? Just call &lt;code&gt;/mint&lt;/code&gt;:&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;POST /mint
Content-Type: application/json

{
  "sub": "username",
  "provider": "local",
  "email": "user@example.com"
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Auth-Flow returns a JWT - no questions asked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is the key design decision:&lt;/strong&gt; Auth-Flow doesn't care &lt;em&gt;how&lt;/em&gt; you authenticated the user. It only cares about issuing and revoking tokens consistently.&lt;/p&gt;

&lt;p&gt;No passwords stored. No session cookies. Clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem: Logout
&lt;/h2&gt;

&lt;p&gt;Here's the dirty secret about JWTs: &lt;strong&gt;they don't support logout.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;JWTs are stateless. You can't "delete" them. Once issued, they're valid until expiration.&lt;/p&gt;

&lt;p&gt;Most tutorials ignore this. Real systems can't.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix: Redis + Token IDs
&lt;/h3&gt;

&lt;p&gt;Every JWT gets a unique ID (&lt;code&gt;jti&lt;/code&gt;). When someone logs out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Store the &lt;code&gt;jti&lt;/code&gt; in Redis with TTL matching token expiration&lt;/li&gt;
&lt;li&gt;Middleware checks Redis on every request&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;jti&lt;/code&gt; exists in Redis, token is revoked, return 401&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instant logout across all services&lt;/li&gt;
&lt;li&gt;No central session store&lt;/li&gt;
&lt;li&gt;Still horizontally scalable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the part most auth libraries skip. Auth-Flow handles it out of the box.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Endpoints
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET  /auth/{provider}
GET  /auth/{provider}/callback
POST /mint
POST /logout
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your other services just need lightweight middleware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify JWT signature&lt;/li&gt;
&lt;li&gt;Check Redis for revocation&lt;/li&gt;
&lt;li&gt;Extract user claims&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it.&lt;/p&gt;

&lt;h2&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%2Fkioxiribamc15s1zendx.png" alt=" " width="800" height="696"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Why Go?
&lt;/h2&gt;

&lt;p&gt;Go is perfect for auth infrastructure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast startup&lt;/strong&gt; — runs in milliseconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low memory&lt;/strong&gt; — handles thousands of requests on minimal resources&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built for concurrency&lt;/strong&gt; — handles OAuth callbacks + Redis + validation in parallel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple&lt;/strong&gt; — the whole service is around 500 lines, easy to audit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus: excellent libraries for OAuth, JWT, and Redis. No framework bloat needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Should You Use This?
&lt;/h2&gt;

&lt;p&gt;This pattern makes sense if you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple backend services (microservices, modular monoliths, etc.)&lt;/li&gt;
&lt;li&gt;Web + mobile clients&lt;/li&gt;
&lt;li&gt;Any need for SSO or centralized user management&lt;/li&gt;
&lt;li&gt;An allergy to reimplementing auth every sprint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not a fit if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have one tiny service and that's it&lt;/li&gt;
&lt;li&gt;You're prototyping and don't care about logout&lt;/li&gt;
&lt;li&gt;You want to store passwords locally (please don't)&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Roadmap ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refresh tokens (currently just access tokens)&lt;/li&gt;
&lt;li&gt;Role-based permissions in JWT claims&lt;/li&gt;
&lt;li&gt;Support for GitHub, Azure AD, custom OIDC providers&lt;/li&gt;
&lt;li&gt;Admin API for token introspection&lt;/li&gt;
&lt;li&gt;Rate limiting on auth endpoints&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Authentication isn't app logic. It's &lt;strong&gt;infrastructure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Stop rebuilding it. Stop copying it. Build it once, properly, and move on.&lt;/p&gt;

&lt;p&gt;Auth-Flow gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OAuth out of the box&lt;/li&gt;
&lt;li&gt;Real logout that actually works&lt;/li&gt;
&lt;li&gt;One source of truth for tokens&lt;/li&gt;
&lt;li&gt;Less code in every other service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're running distributed systems, this saves time, reduces bugs, and keeps security consistent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check it out:&lt;/strong&gt; &lt;a href="https://github.com/abhiram-karanth-core/auth-flow" rel="noopener noreferrer"&gt;https://github.com/abhiram-karanth-core/auth-flow&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Also:&lt;/strong&gt; &lt;a href="https://auth-flow-v1.vercel.app/docs" rel="noopener noreferrer"&gt;https://auth-flow-v1.vercel.app/docs&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Got questions? Built something similar? Hate this approach?&lt;/strong&gt; Drop a comment. I read 'em all.&lt;/p&gt;

</description>
      <category>oauth</category>
      <category>redis</category>
      <category>authentication</category>
      <category>jwt</category>
    </item>
    <item>
      <title>How I built horizontally scalable chat server in Go</title>
      <dc:creator>D Abhiram Karanth</dc:creator>
      <pubDate>Sat, 31 Jan 2026 17:13:41 +0000</pubDate>
      <link>https://dev.to/abhiram_karanth/how-i-built-horizontally-scalable-chat-server-in-go-4ioa</link>
      <guid>https://dev.to/abhiram_karanth/how-i-built-horizontally-scalable-chat-server-in-go-4ioa</guid>
      <description>&lt;p&gt;How I built a horizontally scalable chat server in Go.&lt;/p&gt;

&lt;p&gt;It's not about sending messages fast. &lt;br&gt;
It's about doing three things at once:&lt;br&gt;
— deliver in real time&lt;br&gt;
— preserve order + durability&lt;br&gt;
— scale without sticky sessions&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; Signal-Flow&lt;br&gt;&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/abhiram-karanth-core/signal-flow" rel="noopener noreferrer"&gt;https://github.com/abhiram-karanth-core/signal-flow&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Demo:&lt;/strong&gt; &lt;a href="https://global-chat-app-web.vercel.app/" rel="noopener noreferrer"&gt;https://global-chat-app-web.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's how I broke it down &lt;/p&gt;




&lt;p&gt;The key insight: stop treating a chat message as one thing.&lt;/p&gt;

&lt;p&gt;Every message has a lifecycle with very different requirements at each stage. Collapsing them into one system is where most chat servers break down at scale.&lt;/p&gt;




&lt;p&gt;The lifecycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User sends a message (Next.js)&lt;/li&gt;
&lt;li&gt;Go WebSocket server receives it&lt;/li&gt;
&lt;li&gt;Made durable (Kafka)&lt;/li&gt;
&lt;li&gt;Fanned out to other users (Redis)&lt;/li&gt;
&lt;li&gt;Written to queryable history (Postgres)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each step needs a different system. Each system does exactly one job.&lt;/p&gt;




&lt;p&gt;The first big lesson: real-time delivery and durable storage have opposing needs.&lt;/p&gt;

&lt;p&gt;Real-time = sub-millisecond latency&lt;br&gt;
Durable = safety, ordering, replayability&lt;/p&gt;

&lt;p&gt;Forcing one system to do both means compromising on both. So don't.&lt;/p&gt;




&lt;p&gt;Kafka is the source of truth. Every message hits Kafka first — before fan-out, before anything.&lt;/p&gt;

&lt;p&gt;But Kafka doesn't deliver messages to users. It exists so the system can survive failures and recover state.&lt;/p&gt;

&lt;p&gt;Correctness lives here. Latency does not.&lt;/p&gt;




&lt;p&gt;Why partition by room?&lt;/p&gt;

&lt;p&gt;Message ordering matters — but only &lt;em&gt;within a room&lt;/em&gt;, not globally.&lt;/p&gt;

&lt;p&gt;Kafka guarantees ordering per partition, so messages are partitioned by &lt;code&gt;room_id&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Each room maps deterministically to a single partition, preserving strict ordering&lt;br&gt;
for that room while allowing horizontal throughput across rooms.&lt;/p&gt;

&lt;p&gt;Per partition → ~5k msg/sec&lt;br&gt;&lt;br&gt;
N partitions → ~N × 5k msg/sec across independent rooms&lt;/p&gt;

&lt;p&gt;This keeps chat semantics correct &lt;em&gt;and&lt;/em&gt; scales throughput without global bottlenecks.&lt;/p&gt;




&lt;p&gt;Redis makes it feel instant.&lt;/p&gt;

&lt;p&gt;It sits on the hot path — performing room-based fan-out so that each WebSocket connection independently receives messages for its subscribed room.&lt;/p&gt;

&lt;p&gt;Redis is intentionally ephemeral. If it drops a message: Kafka still has it. Clients recover on reconnect. History stays correct.&lt;/p&gt;




&lt;p&gt;Postgres makes it usable.&lt;/p&gt;

&lt;p&gt;Kafka consumers write to Postgres asynchronously. This means:&lt;br&gt;
— DB slowness never blocks ingestion&lt;br&gt;
— State can be rebuilt from Kafka replay&lt;br&gt;
— Frontend reloads always return consistent history&lt;/p&gt;

&lt;p&gt;Postgres is the materialized view users actually interact with.&lt;/p&gt;




&lt;p&gt;The most important architectural decision: heavy decoupling between consumers.&lt;/p&gt;

&lt;p&gt;Kafka → Redis (real-time fan-out)&lt;br&gt;
Kafka → Postgres (durable writes)&lt;/p&gt;

&lt;p&gt;Producer publishes once. Doesn't care who consumes. Each consumer fails and restarts independently.&lt;/p&gt;

&lt;p&gt;No cascading failures.&lt;/p&gt;

&lt;h2&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%2Fbvuzzxo8zo2nyzydkjov.png" alt="Kafka → Redis (room-based fan-out) → WebSocket connections (one per client)" width="800" height="696"&gt;
&lt;/h2&gt;

&lt;p&gt;Why it scales horizontally:&lt;/p&gt;

&lt;p&gt;— Go servers are stateless (no sticky sessions)&lt;br&gt;
— Real-time delivery is decoupled from durability&lt;br&gt;
— Each component has one job&lt;br&gt;
— Any component can rebuild from Kafka replay&lt;/p&gt;

&lt;p&gt;Scaling becomes an ops concern. Not an app rewrite.&lt;/p&gt;




&lt;p&gt;The frontend never sees any of this.&lt;/p&gt;

&lt;p&gt;WebSockets → real-time updates via Redis&lt;br&gt;
HTTP APIs → message history from Postgres&lt;br&gt;
Kafka stays internal. Clean boundary.&lt;/p&gt;




&lt;p&gt;Don't build a chat server. Build three systems that each do one thing well:&lt;/p&gt;

&lt;p&gt;Kafka → correctness&lt;br&gt;
Redis → latency&lt;br&gt;
Postgres → usability&lt;/p&gt;

&lt;p&gt;Everything else is trade-offs.&lt;/p&gt;

</description>
      <category>go</category>
      <category>redis</category>
      <category>kafka</category>
      <category>postgres</category>
    </item>
  </channel>
</rss>
