<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Mayank's Engineering Notes]]></title><description><![CDATA[Technical articles on backend engineering, system design, databases, DevOps, and software development. Learn from real-world projects, debugging experiences, and production-grade application architecture.]]></description><link>https://mayankdev.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6815b2643a547c35f1fb76bb/559c0d9f-83d6-45a3-8377-65f0f2ef8532.png</url><title>Mayank&apos;s Engineering Notes</title><link>https://mayankdev.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 30 Sep 2026 07:43:24 GMT</lastBuildDate><atom:link href="https://mayankdev.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Binary Tree Traversals: Preorder, Inorder & Postorder Explained]]></title><description><![CDATA[When working with a binary tree, the real question isn't just how to move through its nodes — it's in what order you choose to visit them.
There are three fundamental depth-first search (DFS) traversa]]></description><link>https://mayankdev.hashnode.dev/binary-tree-traversals-preorder-inorder-postorder-explained</link><guid isPermaLink="true">https://mayankdev.hashnode.dev/binary-tree-traversals-preorder-inorder-postorder-explained</guid><category><![CDATA[algorithms]]></category><category><![CDATA[data structures]]></category><category><![CDATA[BinaryTrees]]></category><category><![CDATA[Recursion]]></category><category><![CDATA[DFS]]></category><category><![CDATA[DSA]]></category><category><![CDATA[Tree]]></category><dc:creator><![CDATA[Mayank Mahajan]]></dc:creator><pubDate>Sun, 27 Sep 2026 09:38:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6815b2643a547c35f1fb76bb/a0fb891d-38f5-41ff-94a4-49808d90c340.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>When working with a binary tree, the real question isn't just <em>how</em> to move through its nodes — it's <em>in what order</em> you choose to visit them.</p>
<p>There are three fundamental depth-first search (DFS) traversals:</p>
<ul>
<li><p><strong>Preorder</strong></p>
</li>
<li><p><strong>Inorder</strong></p>
</li>
<li><p><strong>Postorder</strong></p>
</li>
</ul>
<p>All three share the exact same recursive skeleton. The only thing that changes between them is <strong>when the current node gets processed relative to its subtrees</strong>.</p>
<h2>Table of Contents</h2>
<ul>
<li><p><a href="#the-basic-rule">The Basic Rule</a></p>
</li>
<li><p><a href="#preorder-traversal">Preorder Traversal</a></p>
</li>
<li><p><a href="#the-recursion-intuition">The Recursion Intuition</a></p>
</li>
<li><p><a href="#inorder-traversal">Inorder Traversal</a></p>
</li>
<li><p><a href="#postorder-traversal">Postorder Traversal</a></p>
</li>
<li><p><a href="#side-by-side-comparison">Side-by-Side Comparison</a></p>
</li>
<li><p><a href="#time--space-complexity">Time &amp; Space Complexity</a></p>
</li>
<li><p><a href="#how-to-remember-them">How to Remember Them</a></p>
</li>
</ul>
<hr />
<h2>The Basic Rule</h2>
<p>Before comparing the three traversals, internalize one rule that never changes:</p>
<blockquote>
<p><strong>We always move left to right — left subtree before right subtree.</strong></p>
</blockquote>
<p>The <em>only</em> variable is <strong>where the current node is processed</strong> relative to that left-to-right movement:</p>
<pre><code class="language-text">Preorder   →  Root → Left → Right
Inorder    →  Left → Root → Right
Postorder  →  Left → Right → Root
</code></pre>
<p>Once you see where the root falls in each pattern, all three become trivial to derive on the spot — no memorization required.</p>
<p>We'll use this tree as a running example throughout:</p>
<pre><code class="language-text">        1
       / \
      2   3
     / \
    4   5
</code></pre>
<hr />
<h2>Preorder Traversal</h2>
<p><strong>Order:</strong> <code>Root → Left → Right</code></p>
<p>At each node:</p>
<ol>
<li><p>Visit the current node.</p>
</li>
<li><p>Traverse the left subtree.</p>
</li>
<li><p>Traverse the right subtree.</p>
</li>
</ol>
<p><strong>Walkthrough:</strong></p>
<ol>
<li><p>Start at <code>1</code> — it's the root, so process it first.</p>
</li>
<li><p>Descend into the left subtree rooted at <code>2</code>. Process <code>2</code>, then its left child <code>4</code> (no children → return), then its right child <code>5</code>.</p>
</li>
<li><p>With the entire left subtree of <code>1</code> finished, move to the right subtree, <code>3</code>.</p>
</li>
</ol>
<p><strong>Result:</strong></p>
<pre><code class="language-text">1 → 2 → 4 → 5 → 3
</code></pre>
<h3>The Recursion Intuition</h3>
<p>This is the part that makes traversals click.</p>
<p>Every time recursion descends into a child, that child becomes the <strong>root of a smaller subtree</strong> — and the exact same rule (<code>Root → Left → Right</code>) applies again, just at a smaller scale.</p>
<pre><code class="language-text">        1
       /
      2
     /
    4
</code></pre>
<p>When recursion reaches <code>2</code>, it doesn't need new logic — it just re-applies <code>Root → Left → Right</code>:</p>
<pre><code class="language-text">2 → 4 → ...
</code></pre>
<p>And at <code>4</code>, both children are <code>null</code>, so the call returns immediately and control unwinds back up the call stack. This self-similar structure is exactly why recursion maps so naturally onto trees.</p>
<h3>Preorder — C++</h3>
<pre><code class="language-cpp">class Solution {
public:
    void preOrder(TreeNode* root, vector&lt;int&gt;&amp; ans) {
        if (root == nullptr)
            return;

        ans.push_back(root-&gt;val);   // process root first

        preOrder(root-&gt;left, ans);
        preOrder(root-&gt;right, ans);
    }
};
</code></pre>
<p><code>vector&lt;int&gt;&amp; ans</code> is passed <strong>by reference</strong>, so every recursive call accumulates into the same result vector rather than building separate copies.</p>
<h3>Preorder — Java</h3>
<pre><code class="language-java">class Solution {
    public void preOrder(TreeNode root, List&lt;Integer&gt; ans) {
        if (root == null)
            return;

        ans.add(root.val);   // process root first

        preOrder(root.left, ans);
        preOrder(root.right, ans);
    }
}
</code></pre>
<p>The shape of the logic is identical:</p>
<pre><code class="language-text">Process Root → Traverse Left → Traverse Right
</code></pre>
<hr />
<h2>Inorder Traversal</h2>
<p><strong>Order:</strong> <code>Left → Root → Right</code></p>
<p>Only one thing changes from preorder — <em>when</em> the root is processed:</p>
<ol>
<li><p>Traverse the left subtree.</p>
</li>
<li><p>Visit the current node.</p>
</li>
<li><p>Traverse the right subtree.</p>
</li>
</ol>
<p>Using the same tree, the result is:</p>
<pre><code class="language-text">4 → 2 → 5 → 1 → 3
</code></pre>
<p>Compare the two so far:</p>
<pre><code class="language-text">Preorder:  1 → 2 → 4 → 5 → 3
Inorder:   4 → 2 → 5 → 1 → 3
</code></pre>
<p>The tree hasn't changed. The recursive calls haven't changed. <strong>Only the position of the root's processing line moved.</strong></p>
<h3>Inorder — C++</h3>
<pre><code class="language-cpp">class Solution {
public:
    void inOrder(TreeNode* root, vector&lt;int&gt;&amp; ans) {
        if (root == nullptr)
            return;

        inOrder(root-&gt;left, ans);

        ans.push_back(root-&gt;val);   // process root in the middle

        inOrder(root-&gt;right, ans);
    }
};
</code></pre>
<p>Compared to preorder, <code>ans.push_back(root-&gt;val)</code> simply moved <strong>between</strong> the two recursive calls.</p>
<h3>Inorder — Java</h3>
<pre><code class="language-java">class Solution {
    public void inOrder(TreeNode root, List&lt;Integer&gt; ans) {
        if (root == null)
            return;

        inOrder(root.left, ans);

        ans.add(root.val);   // process root in the middle

        inOrder(root.right, ans);
    }
}
</code></pre>
<pre><code class="language-text">Traverse Left → Process Root → Traverse Right
</code></pre>
<hr />
<h2>Postorder Traversal</h2>
<p><strong>Order:</strong> <code>Left → Right → Root</code></p>
<p>The root is processed only <em>after</em> both subtrees are fully traversed:</p>
<ol>
<li><p>Traverse the left subtree.</p>
</li>
<li><p>Traverse the right subtree.</p>
</li>
<li><p>Visit the current node.</p>
</li>
</ol>
<p>Using the same tree, the result is:</p>
<pre><code class="language-text">4 → 5 → 2 → 3 → 1
</code></pre>
<p>Again — same tree, same recursive calls, only the root's processing line moved, this time to the very end.</p>
<h3>Postorder — C++</h3>
<pre><code class="language-cpp">class Solution {
public:
    void postOrder(TreeNode* root, vector&lt;int&gt;&amp; ans) {
        if (root == nullptr)
            return;

        postOrder(root-&gt;left, ans);
        postOrder(root-&gt;right, ans);

        ans.push_back(root-&gt;val);   // process root last
    }
};
</code></pre>
<h3>Postorder — Java</h3>
<pre><code class="language-java">class Solution {
    public void postOrder(TreeNode root, List&lt;Integer&gt; ans) {
        if (root == null)
            return;

        postOrder(root.left, ans);
        postOrder(root.right, ans);

        ans.add(root.val);   // process root last
    }
}
</code></pre>
<pre><code class="language-text">Traverse Left → Traverse Right → Process Root
</code></pre>
<hr />
<h2>Side-by-Side Comparison</h2>
<table>
<thead>
<tr>
<th>Traversal</th>
<th>Order</th>
<th>Root Position</th>
<th>Result (example tree)</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Preorder</strong></td>
<td>Root → Left → Right</td>
<td>First</td>
<td><code>1 → 2 → 4 → 5 → 3</code></td>
</tr>
<tr>
<td><strong>Inorder</strong></td>
<td>Left → Root → Right</td>
<td>Middle</td>
<td><code>4 → 2 → 5 → 1 → 3</code></td>
</tr>
<tr>
<td><strong>Postorder</strong></td>
<td>Left → Right → Root</td>
<td>Last</td>
<td><code>4 → 5 → 2 → 3 → 1</code></td>
</tr>
</tbody></table>
<hr />
<h2>Time &amp; Space Complexity</h2>
<p>All three traversals share the same complexity profile, since each visits every node exactly once:</p>
<ul>
<li><p><strong>Time complexity:</strong> <code>O(n)</code>, where <code>n</code> is the number of nodes.</p>
</li>
<li><p><strong>Space complexity:</strong> <code>O(h)</code> for the recursion call stack, where <code>h</code> is the height of the tree.</p>
<ul>
<li><p>Balanced tree → <code>O(log n)</code>.</p>
</li>
<li><p>Skewed (degenerate) tree → <code>O(n)</code>, since the call stack depth equals the number of nodes.</p>
</li>
</ul>
</li>
</ul>
<p>Note that the output list itself also takes <code>O(n)</code> space, independent of the traversal order.</p>
<hr />
<h2>How to Remember Them</h2>
<p>You don't need to memorize three separate algorithms — just the meaning of the prefixes:</p>
<ul>
<li><p><strong>PRE</strong>order — <em>pre</em> = before → the root comes <strong>first</strong>: <code>Root → Left → Right</code></p>
</li>
<li><p><strong>IN</strong>order — <em>in</em> = in between → the root comes <strong>in the middle</strong>: <code>Left → Root → Right</code></p>
</li>
<li><p><strong>POST</strong>order — <em>post</em> = after → the root comes <strong>last</strong>: <code>Left → Right → Root</code></p>
</li>
</ul>
<p>Combine that with the one constant rule — <strong>left always comes before right</strong> — and the entire family of traversals collapses into a single idea:</p>
<pre><code class="language-text">PRE   → ROOT first
IN    → ROOT in the middle
POST  → ROOT last
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Level Order Traversal: Intuition, Queue, and BFS]]></title><description><![CDATA[How a queue helps us process binary trees level by level, from intuition to implementation.
Level Order Traversal: Why We Walk Trees Breadth-First
When you're asked to process a tree level by level — ]]></description><link>https://mayankdev.hashnode.dev/level-order-traversal-intuition-queue-and-bfs</link><guid isPermaLink="true">https://mayankdev.hashnode.dev/level-order-traversal-intuition-queue-and-bfs</guid><category><![CDATA[DSA]]></category><category><![CDATA[algorithms]]></category><category><![CDATA[binary tree]]></category><category><![CDATA[BFS]]></category><category><![CDATA[Tree]]></category><category><![CDATA[data structures]]></category><category><![CDATA[C++]]></category><category><![CDATA[Java]]></category><category><![CDATA[Python]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Mayank Mahajan]]></dc:creator><pubDate>Fri, 25 Sep 2026 22:15:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6815b2643a547c35f1fb76bb/ed6bd1f9-a0a1-45d6-8590-2a50bb148064.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>How a queue helps us process binary trees level by level, from intuition to implementation.</p>
<h1>Level Order Traversal: Why We Walk Trees Breadth-First</h1>
<p>When you're asked to process a tree <strong>level by level</strong> — floor by floor, row by row, nearest nodes first — the depth-first instinct that works for so many other tree problems stops being useful. This is where <strong>Level Order Traversal</strong>, powered by <strong>Breadth-First Search (BFS)</strong>, comes in.</p>
<h2>The Problem: Depth vs. Breadth</h2>
<p>Take this tree:</p>
<pre><code class="language-text">             1
           /   \
          2     3
         / \   / \
        4   5 6   7
</code></pre>
<p>A <strong>depth-first</strong> traversal would go something like <code>1 → 2 → 4 → 5 → 3 → 6 → 7</code>, diving as far down one branch as it can before backtracking.</p>
<p>A <strong>level-order (breadth-first)</strong> traversal instead visits every node on a given level before moving to the next:</p>
<pre><code class="language-text">Level 0: 1
Level 1: 2 → 3
Level 2: 4 → 5 → 6 → 7
</code></pre>
<h2>An Analogy: The Building Cleaner</h2>
<p>Imagine a cleaner working through a multi-story building. They don't jump between floors at random — they finish the entire first floor, then move to the second, then the third. That's exactly the discipline level order traversal enforces: <strong>finish the current level before starting the next one.</strong></p>
<h2>Why a Queue?</h2>
<p>The trick that makes this traversal work is a <strong>queue</strong>, because a queue is First In, First Out (FIFO) — the same order in which we discover nodes is the order in which we should process them.</p>
<p>The algorithm, in five steps:</p>
<ol>
<li><p>Push the root onto the queue.</p>
</li>
<li><p>While the queue isn't empty, record how many nodes are currently in it — that number is the size of <strong>this</strong> level.</p>
</li>
<li><p>Dequeue that many nodes, one at a time, appending each node's value to the current level's result and enqueuing its left and right children (if they exist).</p>
</li>
<li><p>Once you've dequeued exactly that many nodes, the level is complete — push the level's array onto the final result.</p>
</li>
<li><p>Repeat until the queue is empty.</p>
</li>
</ol>
<h2>Walking Through an Example</h2>
<p>Using the same tree:</p>
<pre><code class="language-text">             1
           /   \
          2     3
         / \   / \
        4   5 6   7
</code></pre>
<table>
<thead>
<tr>
<th>Step</th>
<th>Queue before</th>
<th>Nodes processed this level</th>
<th>Queue after</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><code>[1]</code></td>
<td><code>1</code></td>
<td><code>[2, 3]</code></td>
</tr>
<tr>
<td>2</td>
<td><code>[2, 3]</code></td>
<td><code>2 → 3</code></td>
<td><code>[4, 5, 6, 7]</code></td>
</tr>
<tr>
<td>3</td>
<td><code>[4, 5, 6, 7]</code></td>
<td><code>4 → 5 → 6 → 7</code></td>
<td><code>[]</code></td>
</tr>
</tbody></table>
<p>The final result:</p>
<pre><code class="language-text">[
  [1],
  [2, 3],
  [4, 5, 6, 7]
]
</code></pre>
<h2>Why a <code>for</code> Loop Inside the <code>while</code> Loop?</h2>
<p>This is the detail that trips most people up the first time. Before processing a level, we snapshot the queue's current size:</p>
<pre><code class="language-java">int levelSize = q.size();
</code></pre>
<p>Then we loop exactly that many times:</p>
<pre><code class="language-java">for (int i = 0; i &lt; levelSize; i++) {
    // process current level
}
</code></pre>
<p>Why snapshot the size instead of just looping <code>while (!q.isEmpty())</code>? Because while we process nodes on the current level, we're also <strong>enqueuing their children</strong> — and those children belong to the <em>next</em> level. Without capping the loop at <code>levelSize</code>, we'd start processing children before the current level was finished, and the level boundaries would collapse.</p>
<p>By fixing <code>levelSize</code> up front, we guarantee the <code>for</code> loop touches only the nodes that existed in the queue <em>before</em> any children were added — exactly the current level, nothing more.</p>
<h2>The Core Pattern</h2>
<p>Once this clicks, the whole problem collapses into one chain of reasoning:</p>
<pre><code class="language-text">Level-by-level traversal
        ↓
       BFS
        ↓
      Queue
        ↓
Process queue one level at a time
        ↓
Store each level separately
</code></pre>
<p>Whenever a problem talks about trees in terms of:</p>
<ul>
<li><p>level by level</p>
</li>
<li><p>row by row</p>
</li>
<li><p>floor by floor</p>
</li>
<li><p>nearest node/level first</p>
</li>
</ul>
<p>...that's your signal to reach for BFS with a queue, not recursion or a stack.</p>
<h2>Complexity</h2>
<p><strong>Time — O(n).</strong> Every node is enqueued and dequeued exactly once.</p>
<p><strong>Space — O(n).</strong> In the worst case (a wide, shallow tree), the queue can hold an entire level's worth of nodes — up to roughly <code>n/2</code> for a complete binary tree — and the result array stores all <code>n</code> node values.</p>
<h2>Reference Implementation</h2>
<p><strong>Java</strong></p>
<pre><code class="language-java">public List&lt;List&lt;Integer&gt;&gt; levelOrder(TreeNode root) {
    List&lt;List&lt;Integer&gt;&gt; result = new ArrayList&lt;&gt;();
    if (root == null) return result;

    Queue&lt;TreeNode&gt; queue = new LinkedList&lt;&gt;();
    queue.offer(root);

    while (!queue.isEmpty()) {
        int levelSize = queue.size();
        List&lt;Integer&gt; currentLevel = new ArrayList&lt;&gt;();

        for (int i = 0; i &lt; levelSize; i++) {
            TreeNode node = queue.poll();
            currentLevel.add(node.val);

            if (node.left != null)  queue.offer(node.left);
            if (node.right != null) queue.offer(node.right);
        }

        result.add(currentLevel);
    }

    return result;
}
</code></pre>
<p><strong>Python</strong></p>
<pre><code class="language-python">from collections import deque

def level_order(root):
    result = []
    if not root:
        return result

    queue = deque([root])

    while queue:
        level_size = len(queue)
        current_level = []

        for _ in range(level_size):
            node = queue.popleft()
            current_level.append(node.val)

            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

        result.append(current_level)

    return result
</code></pre>
<h2>Where This Pattern Shows Up Again</h2>
<p>Once level order traversal is second nature, a whole family of tree problems becomes a small variation on the same skeleton:</p>
<ul>
<li><p><strong>Zigzag level order</strong> — alternate the direction you append values in, left-to-right then right-to-left.</p>
</li>
<li><p><strong>Right side view</strong> — keep only the last node processed in each level.</p>
</li>
<li><p><strong>Level averages / sums</strong> — accumulate a running total instead of a list per level.</p>
</li>
<li><p><strong>Minimum depth</strong> — return as soon as you dequeue a node with no children.</p>
</li>
</ul>
<h2>The Main Takeaway</h2>
<p>Don't memorize the code — memorize the trigger. The moment a tree problem asks you to think in terms of levels, the chain is always the same:</p>
<pre><code class="language-text">Level Order → BFS → Queue
</code></pre>
<p>Once that association is automatic, the implementation writes itself.</p>
]]></content:encoded></item><item><title><![CDATA[How I Designed RBAC for a Multi-Role College ERP]]></title><description><![CDATA[From Roles and Permissions to Resource-Level Authorization
From Roles and Permissions to Resource-Level Authorization
While building a multi-role College ERP, I ran into an authorization problem that ]]></description><link>https://mayankdev.hashnode.dev/how-i-designed-rbac-for-a-multi-role-college-erp</link><guid isPermaLink="true">https://mayankdev.hashnode.dev/how-i-designed-rbac-for-a-multi-role-college-erp</guid><category><![CDATA[rbac]]></category><category><![CDATA[authorization]]></category><category><![CDATA[backend]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[System Design]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[college erp]]></category><dc:creator><![CDATA[Mayank Mahajan]]></dc:creator><pubDate>Fri, 25 Sep 2026 09:08:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6815b2643a547c35f1fb76bb/06c76046-5922-44c2-b82d-571ae49c5c4e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>From Roles and Permissions to Resource-Level Authorization</p>
<h2>From Roles and Permissions to Resource-Level Authorization</h2>
<p>While building a <strong>multi-role College ERP</strong>, I ran into an authorization problem that initially looked simple but became much more complicated as the system grew.</p>
<p>In a typical application, we usually start with something like:</p>
<pre><code class="language-text">USER
ADMIN
SUPER_ADMIN
</code></pre>
<p>Then authorization is straightforward:</p>
<pre><code class="language-typescript">if (user.role === "ADMIN") {
  // allow
}
</code></pre>
<p>But a College ERP is very different.</p>
<p>There can be many different types of users:</p>
<ul>
<li><p>Student</p>
</li>
<li><p>Faculty</p>
</li>
<li><p>HOD</p>
</li>
<li><p>Clerk</p>
</li>
<li><p>Accountant</p>
</li>
<li><p>Exam Officer</p>
</li>
<li><p>Admission Officer</p>
</li>
<li><p>Principal</p>
</li>
<li><p>Director</p>
</li>
<li><p>Secretary</p>
</li>
<li><p>Super Admin</p>
</li>
</ul>
<p>And the important part is that these roles are not necessarily fixed forever.</p>
<p>The institution may need to create a new role later.</p>
<p>For example:</p>
<ul>
<li><p>Lab Coordinator</p>
</li>
<li><p>Library Administrator</p>
</li>
<li><p>Department Coordinator</p>
</li>
<li><p>Placement Officer</p>
</li>
<li><p>Hostel Warden</p>
</li>
</ul>
<p>So the real question was no longer:</p>
<blockquote>
<p>"What role does this user have?"</p>
</blockquote>
<p>It became:</p>
<blockquote>
<p>"What is this role allowed to do?"</p>
</blockquote>
<p>And then an even harder question appeared:</p>
<blockquote>
<p>"What is this role allowed to do, on which resource, and under what conditions?"</p>
</blockquote>
<p>That is where I started moving from simple role-based checks toward a proper roles → permissions → resource-level authorization model.</p>
<h2>The Problem With Traditional Role Checks</h2>
<p>My first thought was to define roles directly in the application:</p>
<pre><code class="language-typescript">enum Role {
  STUDENT,
  FACULTY,
  HOD,
  PRINCIPAL,
  ADMIN,
  SUPER_ADMIN
}
</code></pre>
<p>Then I could simply check:</p>
<pre><code class="language-typescript">if (user.role === "FACULTY") {
  // faculty access
}
</code></pre>
<p>This works initially.</p>
<p>But imagine the ERP grows.</p>
<p>Now we have 20 roles.</p>
<p>And each role needs different access.</p>
<p>For example:</p>
<table>
<thead>
<tr>
<th>Role</th>
<th>Students</th>
<th>Attendance</th>
<th>Fees</th>
<th>Faculty</th>
<th>Reports</th>
</tr>
</thead>
<tbody><tr>
<td>Student</td>
<td>Own</td>
<td>Own</td>
<td>Own</td>
<td>No</td>
<td>Own</td>
</tr>
<tr>
<td>Faculty</td>
<td>View</td>
<td>Manage</td>
<td>No</td>
<td>No</td>
<td>Department</td>
</tr>
<tr>
<td>HOD</td>
<td>Manage</td>
<td>Manage</td>
<td>View</td>
<td>Manage</td>
<td>Department</td>
</tr>
<tr>
<td>Accountant</td>
<td>View</td>
<td>No</td>
<td>Manage</td>
<td>No</td>
<td>Financial</td>
</tr>
<tr>
<td>Principal</td>
<td>Manage</td>
<td>View</td>
<td>Manage</td>
<td>Manage</td>
<td>Institution</td>
</tr>
<tr>
<td>Super Admin</td>
<td>All</td>
<td>All</td>
<td>All</td>
<td>All</td>
<td>All</td>
</tr>
</tbody></table>
<p>Now the code starts becoming:</p>
<pre><code class="language-typescript">if (
  user.role === "ADMIN" ||
  user.role === "PRINCIPAL" ||
  user.role === "HOD"
) {
  // allow
}
</code></pre>
<p>Then another requirement arrives:</p>
<blockquote>
<p>Faculty should be able to create something, but only for their department.</p>
</blockquote>
<p>Now the role itself isn't enough.</p>
<p>We need to know:</p>
<pre><code class="language-text">Who is the user?
        ↓
What role do they have?
        ↓
What permissions does that role have?
        ↓
Which resource are they accessing?
        ↓
Are they allowed to access THIS resource?
</code></pre>
<p>That changed how I thought about authorization.</p>
<h2>Roles Should Not Define Everything</h2>
<p>One of the important decisions I made was separating roles from permissions.</p>
<p>Instead of thinking:</p>
<pre><code class="language-text">Faculty = Can do X
HOD = Can do Y
Admin = Can do Z
</code></pre>
<p>I started thinking:</p>
<pre><code class="language-text">Role
  ↓
Permissions
  ↓
Resources
</code></pre>
<p>For example:</p>
<pre><code class="language-text">FACULTY
   │
   ├── student.read
   ├── attendance.read
   ├── attendance.create
   └── attendance.update
</code></pre>
<p>While:</p>
<pre><code class="language-text">HOD
   │
   ├── student.read
   ├── student.create
   ├── student.update
   ├── attendance.manage
   ├── faculty.read
   └── reports.read
</code></pre>
<p>And:</p>
<pre><code class="language-text">ACCOUNTANT
   │
   ├── student.read
   ├── fees.read
   ├── fees.create
   ├── fees.update
   └── financial-reports.read
</code></pre>
<p>Now the role is simply a collection of permissions.</p>
<h2>The Interesting Problem: Roles Can Be Created Dynamically</h2>
<p>This was one of the bigger requirements in my ERP.</p>
<p>I didn't want to hard-code every possible role into the backend.</p>
<p>For example, I didn't want this:</p>
<pre><code class="language-typescript">enum Role {
  STUDENT,
  FACULTY,
  HOD,
  CLERK,
  ACCOUNTANT,
  PRINCIPAL,
  SUPER_ADMIN
}
</code></pre>
<p>because tomorrow the institution might need:</p>
<ul>
<li><p>Placement Officer</p>
</li>
<li><p>Department Coordinator</p>
</li>
<li><p>Hostel Warden</p>
</li>
</ul>
<p>I wanted the system to support:</p>
<pre><code class="language-text">Create Role
      ↓
Select Permissions
      ↓
Save Role
      ↓
Assign Role to Users
</code></pre>
<p>So a role becomes data stored in the database rather than something that has to be added to the source code every time.</p>
<p>For example:</p>
<pre><code class="language-text">Role: Department Coordinator

Permissions:

student.read
student.update
attendance.read
attendance.update
reports.read
</code></pre>
<p>The backend doesn't need a new <code>DEPARTMENT_COORDINATOR</code> enum just to support it.</p>
<h2>But Then Another Problem Appeared</h2>
<p>If roles can be created dynamically, who is allowed to create a role?</p>
<p>This is where authorization becomes interesting.</p>
<p>Suppose a Faculty member sends:</p>
<pre><code class="language-text">POST /api/roles
</code></pre>
<p>with:</p>
<pre><code class="language-json">{
  "name": "Super Admin"
}
</code></pre>
<p>Should the backend allow it?</p>
<p>Obviously, the answer depends on the permissions assigned to that faculty member.</p>
<p>So checking:</p>
<pre><code class="language-typescript">user.role === "FACULTY"
</code></pre>
<p>isn't enough.</p>
<p>We need something closer to:</p>
<pre><code class="language-text">User
 ↓
Role
 ↓
Permissions
 ↓
Can this permission be executed?
</code></pre>
<p>For example:</p>
<pre><code class="language-text">role.create
</code></pre>
<p>Now the API can ask:</p>
<blockquote>
<p>Does this user have <code>role.create</code> permission?</p>
</blockquote>
<p>instead of asking:</p>
<blockquote>
<p>Is this user an admin?</p>
</blockquote>
<p>That is a much more flexible model.</p>
<h2>Permissions as Actions</h2>
<p>I decided to think about permissions as an action performed on a resource.</p>
<p>A simple format is:</p>
<pre><code class="language-text">resource.action
</code></pre>
<p>For example:</p>
<pre><code class="language-text">student.read
student.create
student.update
student.delete

faculty.read
faculty.create
faculty.update
faculty.delete

role.read
role.create
role.update
role.delete

attendance.read
attendance.create
attendance.update

fees.read
fees.create
fees.update
</code></pre>
<p>This gives the authorization system a consistent vocabulary.</p>
<p>Instead of writing:</p>
<pre><code class="language-js">if (user.role === "ADMIN") {
   // ...
}
</code></pre>
<p>the application can ask:</p>
<pre><code class="language-js">hasPermission(user, "role.create");
</code></pre>
<p>That makes the authorization logic much easier to reason about.</p>
<h2>The Architecture</h2>
<p>The basic relationship becomes:</p>
<pre><code class="language-text">                    USER
                      │
                      ↓
                    ROLE
                      │
                      ↓
                PERMISSIONS
                      │
                      ↓
                  RESOURCE
</code></pre>
<p>Or, if a user can have multiple roles:</p>
<pre><code class="language-text">                    USER
                     │
             ┌───────┴───────┐
             ↓               ↓
          FACULTY            HOD
             │               │
             └───────┬───────┘
                     ↓
               PERMISSIONS
                     │
                     ↓
                  RESOURCE
</code></pre>
<p>For example:</p>
<pre><code class="language-text">User: Rahul

Roles:
    Faculty
    Department Coordinator

Effective permissions:

    student.read
    student.update
    attendance.read
    attendance.update
    reports.read
</code></pre>
<p>The user's effective permissions can be derived from all assigned roles.</p>
<h2>But RBAC Still Wasn't Enough</h2>
<p>This is where the problem became more interesting.</p>
<p>Imagine a Faculty member has:</p>
<pre><code class="language-text">student.read
student.update
</code></pre>
<p>Does that mean they can update every student in the college?</p>
<p>Probably not.</p>
<p>A faculty member may only be responsible for students in their department.</p>
<p>For example:</p>
<pre><code class="language-text">Faculty
Department: Information Technology

Student:

Student A
Department: Information Technology

Allowed.

But:

Student B
Department: Computer Science

Maybe not allowed.
</code></pre>
<p>Now permission alone isn't enough.</p>
<p>We need to consider the resource itself.</p>
<p>The authorization decision becomes:</p>
<pre><code class="language-text">Does the user have the permission?
                +
Does the user have access to this resource?
</code></pre>
<p>This is where I started thinking about resource-level authorization.</p>
<h2>From RBAC to Resource-Level Authorization</h2>
<p>Instead of:</p>
<blockquote>
<p>Can Faculty update students?</p>
</blockquote>
<p>the question becomes:</p>
<blockquote>
<p>Can this Faculty member update THIS student?</p>
</blockquote>
<p>For example:</p>
<pre><code class="language-js">const hasPermission =
  user.permissions.includes("student.update");

const sameDepartment =
  user.departmentId === student.departmentId;

if (!hasPermission || !sameDepartment) {
  throw new ForbiddenError();
}
</code></pre>
<p>Now authorization considers both:</p>
<pre><code class="language-text">Permission
+
Resource context
</code></pre>
<p>This is much closer to how a real ERP needs to work.</p>
<h2>Why This Matters</h2>
<p>Consider an HOD.</p>
<p>An HOD might have:</p>
<pre><code class="language-text">student.read
student.create
student.update
</code></pre>
<p>But their authority could be limited to their own department.</p>
<p>So:</p>
<pre><code class="language-text">HOD
 │
 ├── student.update
 │
 └── Department = Information Technology
</code></pre>
<p>When the HOD requests:</p>
<pre><code class="language-text">PATCH /students/123
</code></pre>
<p>the backend should not simply check:</p>
<pre><code class="language-js">hasPermission("student.update");
</code></pre>
<p>It should also determine:</p>
<pre><code class="language-text">Student 123
     ↓
Which department?
     ↓
Does HOD have authority over that department?
</code></pre>
<p>The final decision becomes:</p>
<pre><code class="language-text">Permission ✓
        +
Resource Scope ✓
        ↓
      ALLOW
</code></pre>
<p>Otherwise:</p>
<pre><code class="language-text">Permission ✓
        +
Resource Scope ✗
        ↓
      DENY
</code></pre>
<h2>The Authorization Pipeline</h2>
<p>The architecture I started moving toward looks like this:</p>
<pre><code class="language-text">HTTP Request
     │
     ↓
Authentication
     │
     ↓
Identify User
     │
     ↓
Load Roles
     │
     ↓
Resolve Permissions
     │
     ↓
Permission Check
     │
     ↓
Resource-Level Check
     │
 ┌───┴────┐
 ↓        ↓
ALLOW    DENY
 ↓        ↓
Service   403
 ↓
Database
</code></pre>
<p>This gives each layer a clear responsibility.</p>
<p>Authentication answers:</p>
<blockquote>
<p>Who is this user?</p>
</blockquote>
<p>Permission check answers:</p>
<blockquote>
<p>Is this user allowed to perform this action?</p>
</blockquote>
<p>Resource authorization answers:</p>
<blockquote>
<p>Is this user allowed to perform this action on this particular resource?</p>
</blockquote>
<h2>One of the Most Important Lessons</h2>
<p>The biggest lesson for me was that roles should not be treated as permissions.</p>
<p>A role is a grouping mechanism.</p>
<p>A permission represents an allowed action.</p>
<p>And resource-level authorization determines whether that action is valid for a particular resource or context.</p>
<p>So instead of:</p>
<pre><code class="language-text">Faculty → Can update students
</code></pre>
<p>the model becomes:</p>
<pre><code class="language-text">Faculty
   ↓
student.update
   ↓
Student #123
   ↓
Department / ownership / scope check
   ↓
ALLOW or DENY
</code></pre>
<p>That separation makes the system much easier to extend.</p>
<p>If tomorrow I create:</p>
<pre><code class="language-text">Placement Officer
</code></pre>
<p>I don't need to change authorization logic everywhere.</p>
<p>I can create the role and assign:</p>
<pre><code class="language-text">student.read
placement.create
placement.update
placement.read
reports.read
</code></pre>
<p>The same authorization engine can continue working.</p>
<h2>Where I Am Taking This Architecture</h2>
<p>For a College ERP, I don't want authorization to become a collection of:</p>
<pre><code class="language-js">if (role === ...)
</code></pre>
<p>statements scattered across controllers.</p>
<p>I want it to become a consistent system:</p>
<pre><code class="language-text">User
  ↓
Roles
  ↓
Permissions
  ↓
Resource Scope
  ↓
Authorization Decision
</code></pre>
<p>This gives the ERP room to grow from a handful of roles to dozens of roles without having to redesign authorization every time a new administrative responsibility appears.</p>
<p>And that was the real shift in my thinking:</p>
<p><strong>I wasn't really trying to design roles. I was designing an authorization system.</strong></p>
<p>That distinction became important once the application started dealing with multiple departments, multiple resources, dynamically created roles, and different levels of access.</p>
]]></content:encoded></item><item><title><![CDATA[How I Built a Unified Authentication System with Password, Google OAuth & GitHub]]></title><description><![CDATA[Building Unified Authentication in Node.js: Password + Google OAuth + GitHub
While building a custom authentication system with Google OAuth, GitHub OAuth, and traditional email/password authenticatio]]></description><link>https://mayankdev.hashnode.dev/how-i-built-a-unified-authentication-system-with-password-google-oauth-github</link><guid isPermaLink="true">https://mayankdev.hashnode.dev/how-i-built-a-unified-authentication-system-with-password-google-oauth-github</guid><category><![CDATA[Node.js]]></category><category><![CDATA[authentication]]></category><category><![CDATA[oauth]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[backend]]></category><category><![CDATA[backend developments]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[architecture]]></category><dc:creator><![CDATA[Mayank Mahajan]]></dc:creator><pubDate>Thu, 24 Sep 2026 12:05:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6815b2643a547c35f1fb76bb/2d478453-1475-4678-9d8f-c170829e7dd9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Building Unified Authentication in Node.js: Password + Google OAuth + GitHub</h1>
<p>While building a custom authentication system with <strong>Google OAuth, GitHub OAuth, and traditional email/password authentication</strong>, I encountered an interesting problem:</p>
<blockquote>
<p><strong>How can we allow the same user to authenticate through multiple providers without creating duplicate accounts or treating each provider as a separate user?</strong></p>
</blockquote>
<p>Initially, my authentication system treated each authentication method independently. This worked when a user consistently used the same provider, but caused problems when the same user switched providers.</p>
<hr />
<h2>The Problem I Encountered</h2>
<p>Consider this scenario.</p>
<p>A user visits the application for the first time and chooses:</p>
<p><strong>Continue with Google</strong></p>
<p>Google authenticates the user and returns information such as:</p>
<ul>
<li><p>Email</p>
</li>
<li><p>Name</p>
</li>
<li><p>Profile picture</p>
</li>
<li><p>Google account ID</p>
</li>
</ul>
<p>The application creates a user account:</p>
<pre><code class="language-text">┌─────────────────────────────┐
│            User             │
├─────────────────────────────┤
│ id:    usr_123              │
│ email: user@example.com     │
│ name:  John Doe             │
└─────────────────────────────┘
</code></pre>
<p>Everything works.</p>
<p>The next day, the same user visits the application but chooses:</p>
<p><strong>Continue with GitHub</strong></p>
<p>GitHub returns:</p>
<ul>
<li><p>Email: <code>user@example.com</code></p>
</li>
<li><p>GitHub Account ID: <code>github_98765</code></p>
</li>
</ul>
<p>The application checks the database and finds <code>user@example.com</code>.</p>
<p>Because that email already exists, the system returns:</p>
<pre><code class="language-text">Email already linked
</code></pre>
<p>From the application's perspective, this seems reasonable.</p>
<p>But from the user's perspective, it is incorrect.</p>
<p>The user is still the same person. They simply used a different authentication provider.</p>
<hr />
<h2>The Real Problem</h2>
<p>The initial architecture was effectively treating authentication providers as separate users:</p>
<pre><code class="language-text">          Authentication
                │
       ┌────────┼────────┐
       ↓        ↓        ↓
    Google    GitHub   Password
       │        │        │
       ↓        ↓        ↓
     User     User     User
</code></pre>
<p>What we actually need is:</p>
<pre><code class="language-text">                    ┌──────────────┐
                    │     User     │
                    │   usr_123    │
                    └──────┬───────┘
                           │
              ┌────────────┼────────────┐
              ↓            ↓            ↓
          Google         GitHub      Password
          Identity       Identity    Credential
</code></pre>
<p>The important distinction is:</p>
<blockquote>
<p><strong>A user account is not the same thing as an authentication method.</strong></p>
</blockquote>
<hr />
<h2>OAuth and Custom Authentication Work Differently</h2>
<p>One of the things that made this problem interesting is that OAuth authentication and traditional email/password authentication have different flows.</p>
<h3>OAuth Authentication</h3>
<p>With Google or GitHub, the OAuth provider handles the authentication process.</p>
<p>The basic flow looks like this:</p>
<pre><code class="language-text">┌──────────┐
│   User   │
└────┬─────┘
     │
     │ Continue with Google/GitHub
     ↓
┌──────────────┐
│ OAuth        │
│ Provider     │
└────┬─────────┘
     │
     │ Authenticate user
     ↓
┌──────────────────────┐
│ Application Callback │
└──────────┬───────────┘
           │
           │ Provider identity
           ↓
      Find/Create User
           │
           ↓
      Create Session
           │
           ↓
       Application
</code></pre>
<p>Therefore, OAuth can effectively handle both registration and login through the same flow.</p>
<p>If the user doesn't exist:</p>
<pre><code class="language-text">OAuth
  ↓
User doesn't exist
  ↓
Create User
  ↓
Create Identity
  ↓
Create Session
  ↓
Application
</code></pre>
<p>If the user already exists:</p>
<pre><code class="language-text">OAuth
  ↓
User exists
  ↓
Authenticate
  ↓
Create Session
  ↓
Application
</code></pre>
<p>The user doesn't necessarily need separate:</p>
<ul>
<li><p><strong>Register with Google</strong></p>
</li>
<li><p><strong>Login with Google</strong></p>
</li>
</ul>
<p>buttons.</p>
<p>The application can determine whether the authenticated identity already exists.</p>
<h3>Traditional Email/Password Authentication</h3>
<p>Custom authentication usually has separate flows.</p>
<h4>Registration</h4>
<pre><code class="language-text">User
  ↓
Registration Page
  ↓
Email + Password
  ↓
Validate Input
  ↓
Hash Password
  ↓
Create User
  ↓
Create Password Credential
</code></pre>
<h4>Login</h4>
<pre><code class="language-text">User
  ↓
Login Page
  ↓
Email + Password
  ↓
Verify Credentials
  ↓
Create Session
  ↓
Application
</code></pre>
<p>The exact flow depends on the application's business requirements.</p>
<hr />
<h2>Authentication vs Profile Completion</h2>
<p>Another problem appears when OAuth does not provide all the information required by the application.</p>
<p>For example, Google might provide:</p>
<ul>
<li><p>Email</p>
</li>
<li><p>Name</p>
</li>
<li><p>Profile Picture</p>
</li>
<li><p>Google Account ID</p>
</li>
</ul>
<p>But the application might require:</p>
<ul>
<li><p>Phone Number</p>
</li>
<li><p>Department</p>
</li>
<li><p>Organization</p>
</li>
<li><p>Date of Birth</p>
</li>
<li><p>Address</p>
</li>
</ul>
<p>Those fields cannot simply be expected from the OAuth provider.</p>
<p>Therefore:</p>
<blockquote>
<p><strong>Authentication and profile completion should be treated as two separate concepts.</strong></p>
</blockquote>
<p>The flow becomes:</p>
<pre><code class="language-text">             OAuth Authentication
                      │
                      ↓
              User authenticated
                      │
                      ↓
              Is profile complete?
                 /           \
               Yes            No
                │              │
                ↓              ↓
           Dashboard      Complete Profile
                               │
                               ↓
                           Dashboard
</code></pre>
<p>This allows the user to authenticate immediately while still requiring application-specific information later.</p>
<hr />
<h2>Another Problem: Password Authentication After OAuth</h2>
<p>Consider another situation.</p>
<p>A user initially creates an account using Google:</p>
<pre><code class="language-text">Google OAuth
     ↓
User created
</code></pre>
<p>The user never created a password.</p>
<p>The database may therefore contain:</p>
<pre><code class="language-text">User
────────────────────────
email: user@example.com
password: NULL
</code></pre>
<p>If the user later tries <strong>Email + Password</strong>, there is no password credential to verify.</p>
<p>Therefore, the application needs a <strong>Set Password / Add Password</strong> flow.</p>
<p>For example:</p>
<pre><code class="language-text">Google Account
      │
      ↓
Existing User
      │
      ↓
Set Password
      │
      ↓
Password Authentication Enabled
</code></pre>
<p>After this, the same user can authenticate through multiple methods.</p>
<hr />
<h2>The Solution: One User, Multiple Identities</h2>
<p>The main architectural change is:</p>
<blockquote>
<p><strong>A user account should not be tied to a single authentication provider.</strong></p>
</blockquote>
<p>Instead:</p>
<pre><code class="language-text">                         ┌──────────────┐
                         │     USER     │
                         │   usr_123    │
                         └──────┬───────┘
                                │
              ┌─────────────────┼─────────────────┐
              │                 │                 │
              ↓                 ↓                 ↓
        ┌───────────┐     ┌───────────┐     ┌───────────┐
        │  Google   │     │  GitHub   │     │ Password  │
        │ Identity  │     │ Identity  │     │ Credential│
        └───────────┘     └───────────┘     └───────────┘
</code></pre>
<p>The <strong>User</strong> represents the actual application account.</p>
<p>The <strong>linked identities</strong> represent the ways that user can authenticate.</p>
<hr />
<h2>Recommended Database Model</h2>
<p>Instead of storing everything directly inside the <code>User</code> record, use a separate <code>Account</code> or <code>Identity</code> table.</p>
<p>For example:</p>
<pre><code class="language-text">┌──────────────────────────────┐
│             User             │
├──────────────────────────────┤
│ id                           │
│ email                        │
│ name                         │
│ avatar                       │
│ profileCompleted             │
│ createdAt                    │
│ updatedAt                    │
└──────────────┬───────────────┘
               │
               │ 1 : N
               ↓
┌──────────────────────────────┐
│           Account            │
├──────────────────────────────┤
│ id                           │
│ userId                       │
│ provider                     │
│ providerAccountId            │
│ createdAt                    │
│ updatedAt                    │
└──────────────────────────────┘
</code></pre>
<p>The relationship is:</p>
<pre><code class="language-text">User 1 ─────────── N Accounts
</code></pre>
<h3>Example Database Records</h3>
<p>Suppose a user initially registers with Google.</p>
<p><strong>User</strong></p>
<pre><code class="language-text">┌──────────────────────────────┐
│ User                         │
├──────────────────────────────┤
│ id:    usr_123               │
│ email: user@example.com      │
│ name:  John Doe              │
└──────────────────────────────┘
</code></pre>
<p><strong>Google Account</strong></p>
<pre><code class="language-text">┌──────────────────────────────┐
│ Account                      │
├──────────────────────────────┤
│ userId:            usr_123   │
│ provider:          GOOGLE    │
│ providerAccountId: google_1  │
└──────────────────────────────┘
</code></pre>
<p>Later, the same user authenticates with GitHub:</p>
<pre><code class="language-text">┌──────────────────────────────┐
│ Account                      │
├──────────────────────────────┤
│ userId:            usr_123   │
│ provider:          GITHUB    │
│ providerAccountId: github_1  │
└──────────────────────────────┘
</code></pre>
<p>Now the database represents:</p>
<pre><code class="language-plaintext">                    User #123
                       │
              ┌────────┴────────┐
              │                 │
              ↓                 ↓
           Google             GitHub
          Account             Account
</code></pre>
<p>Both identities point to <code>userId = usr_123</code>.</p>
<p>Therefore, they represent the same application user.</p>
<hr />
<h2>My Initial Idea</h2>
<p>Initially, I thought about storing the authentication providers directly inside the user object:</p>
<pre><code class="language-typescript">{
  authProvider: {
    google: {
      id: "google-account-id"
    },
    github: {
      id: "github-account-id"
    },
    custom: {
      id: "email"
    }
  }
}
</code></pre>
<p>Conceptually, this represents what I wanted:</p>
<pre><code class="language-text">                 User
                  │
       ┌──────────┼──────────┐
       ↓          ↓          ↓
    Google      GitHub     Custom
</code></pre>
<p>However, for a relational database, I found that a separate Account/Identity table is a better representation.</p>
<p>It allows the relationship to naturally become:</p>
<pre><code class="language-text">User 1 ──────────── N Accounts
</code></pre>
<p>instead of continuously adding authentication providers to the user record.</p>
<p>It also makes it easier to:</p>
<ul>
<li><p>Add new providers</p>
</li>
<li><p>Enforce unique provider identities</p>
</li>
<li><p>Query linked accounts</p>
</li>
<li><p>Unlink providers</p>
</li>
<li><p>Manage provider-specific identifiers</p>
</li>
<li><p>Maintain a clean database structure</p>
</li>
</ul>
<hr />
<h2>The Unified Authentication Flow</h2>
<p>The complete flow can be represented as:</p>
<pre><code class="language-text">                    ┌─────────────────────┐
                    │ Authentication      │
                    │ Request             │
                    └──────────┬──────────┘
                               │
             ┌─────────────────┼─────────────────┐
             ↓                 ↓                 ↓
        ┌─────────┐       ┌─────────┐       ┌─────────┐
        │Password │       │ Google  │       │ GitHub  │
        └────┬────┘       └────┬────┘       └────┬────┘
             │                 │                 │
             └─────────────────┼─────────────────┘
                               ↓
                    ┌─────────────────────┐
                    │ Authenticate        │
                    │ Identity            │
                    └──────────┬──────────┘
                               ↓
                    ┌─────────────────────┐
                    │ Find linked Account │
                    └──────────┬──────────┘
                               │
                     ┌─────────┴─────────┐
                     ↓                   ↓
                   Found              Not Found
                     │                   │
                     ↓                   ↓
                Get User          Create or securely
                                  link Account
                     │                   │
                     └─────────┬─────────┘
                               ↓
                         ┌───────────┐
                         │   User    │
                         └─────┬─────┘
                               ↓
                    ┌─────────────────────┐
                    │ Profile Complete?   │
                    └──────────┬──────────┘
                               │
                      ┌────────┴────────┐
                      ↓                 ↓
                    Yes                No
                      │                 │
                      ↓                 ↓
                 Dashboard        Profile Setup
</code></pre>
<hr />
<h2>Example: Google → GitHub</h2>
<p>Let's walk through the original problem using the new architecture.</p>
<h3>First Login — Google</h3>
<pre><code class="language-text">User
 ↓
Google OAuth
 ↓
Google verifies identity
 ↓
No Google Account exists
 ↓
Create User
 ↓
Create Google Account
 ↓
Create Session
</code></pre>
<p>Database:</p>
<pre><code class="language-text">User #123
   │
   └── Google Account
</code></pre>
<h3>Second Login — GitHub</h3>
<p>The same user chooses GitHub:</p>
<pre><code class="language-text">User
 ↓
GitHub OAuth
 ↓
GitHub verifies identity
 ↓
No GitHub Account exists
 ↓
Check account-linking rules
 ↓
Link GitHub Account to User #123
</code></pre>
<p>Database:</p>
<pre><code class="language-text">User #123
   │
   ├── Google Account
   │
   └── GitHub Account
</code></pre>
<p>No second user needs to be created.</p>
<hr />
<h2>Adding Password Authentication</h2>
<p>The user can later explicitly create a password for the existing account.</p>
<pre><code class="language-text">User #123
   │
   ├── Google Account
   ├── GitHub Account
   └── Password Credential
</code></pre>
<p>Now all three authentication methods eventually resolve to <code>User #123</code>.</p>
<hr />
<h2>Account Linking Requires Security</h2>
<p>One important lesson is that we should <strong>not</strong> blindly merge accounts simply because two providers return the same email address.</p>
<p>A safer flow is:</p>
<pre><code class="language-text">OAuth Authentication
        │
        ↓
Verify Provider Identity
        │
        ↓
Existing Linked Identity?
       / \
     Yes  No
      │    │
      │    ↓
      │  Existing User?
      │    / \
      │  No   Yes
      │   │     │
      │   ↓     ↓
      │ Create  Secure
      │ User    Linking Flow
      │   │     │
      └───┴─────┘
            ↓
           User
</code></pre>
<p>The exact linking policy depends on the application.</p>
<p>The system should also consider:</p>
<ul>
<li><p>Verified provider email</p>
</li>
<li><p>OAuth state</p>
</li>
<li><p>CSRF protection where applicable</p>
</li>
<li><p>Secure cookies</p>
</li>
<li><p>Session expiration</p>
</li>
<li><p>Session revocation</p>
</li>
<li><p>Password hashing</p>
</li>
<li><p>Rate limiting</p>
</li>
<li><p>Provider account IDs</p>
</li>
<li><p>Explicit account-linking flows</p>
</li>
</ul>
<p>This prevents an external identity from being incorrectly linked to the wrong application account.</p>
<hr />
<h2>Authentication, Identity, and Session Are Different</h2>
<p>One of the biggest architectural lessons from this problem was that these concepts should not be treated as the same thing.</p>
<pre><code class="language-text">┌──────────────────────┐
│         User         │
│                      │
│ Application Account  │
└──────────┬───────────┘
           │
           │ has
           ↓
┌──────────────────────┐
│ Identity / Account   │
│                      │
│ How the user         │
│ authenticates        │
└──────────┬───────────┘
           │
           │ creates
           ↓
┌──────────────────────┐
│       Session        │
│                      │
│ Current authenticated│
│ access               │
└──────────────────────┘
</code></pre>
<p>For example:</p>
<pre><code class="language-text">                    User #123
                        │
           ┌────────────┼────────────┐
           ↓            ↓            ↓
        Google        GitHub       Password
        Identity      Identity     Credential
           │            │            │
           └────────────┼────────────┘
                        ↓
                     Session
                        ↓
                 Authenticated App
</code></pre>
<p>The session ultimately belongs to the application user, not to Google or GitHub.</p>
<hr />
<h2>Final Architecture</h2>
<p>The final architecture I moved toward looks like this:</p>
<pre><code class="language-text">                         ┌──────────────┐
                         │     USER     │
                         │   usr_123    │
                         └──────┬───────┘
                                │
                    ┌───────────┼───────────┐
                    │           │           │
                    ↓           ↓           ↓
              ┌──────────┐ ┌─────────┐ ┌──────────┐
              │  Google  │ │ GitHub  │ │ Password │
              │ Identity │ │Identity │ │Credential│
              └──────────┘ └─────────┘ └──────────┘
                    │           │           │
                    └───────────┼───────────┘
                                ↓
                         ┌──────────────┐
                         │   SESSION    │
                         └──────┬───────┘
                                ↓
                       Authenticated User
                                │
                       ┌────────┴────────┐
                       ↓                 ↓
                 Profile Complete    Profile Missing
                       │                 │
                       ↓                 ↓
                   Dashboard        Onboarding
</code></pre>
<hr />
<h2>The Main Lesson</h2>
<p>The most important lesson I learned from this implementation is:</p>
<blockquote>
<p><strong>Authentication providers should identify a user, not define the user.</strong></p>
</blockquote>
<p>A single application user can have multiple authentication identities:</p>
<pre><code class="language-text">                    ONE USER
                       │
          ┌────────────┼────────────┐
          ↓            ↓            ↓
       Google        GitHub       Password
          │            │            │
          └────────────┼────────────┘
                       ↓
                    Session
                       ↓
                Application
</code></pre>
<p>Once this distinction is made, several problems become much easier to reason about:</p>
<ul>
<li><p>Multiple OAuth providers</p>
</li>
<li><p>Custom email/password authentication</p>
</li>
<li><p>Account linking</p>
</li>
<li><p>Duplicate account prevention</p>
</li>
<li><p>Profile completion</p>
</li>
<li><p>Password setup after OAuth</p>
</li>
<li><p>Session management</p>
</li>
<li><p>Provider-specific identities</p>
</li>
</ul>
<p>The key is to model <strong>User</strong>, <strong>Identity/Credential</strong>, and <strong>Session</strong> as separate concepts instead of treating every authentication method as a separate user account.</p>
]]></content:encoded></item><item><title><![CDATA[10 Line Code vs 100 Line Code: Which Is Better?]]></title><description><![CDATA[Many developers assume that shorter code is automatically more efficient or has better time complexity. However, that's not always true—it really depends on the specific case.
Let’s consider a simple ]]></description><link>https://mayankdev.hashnode.dev/10-line-code-vs-100-line-code-which-is-better</link><guid isPermaLink="true">https://mayankdev.hashnode.dev/10-line-code-vs-100-line-code-which-is-better</guid><category><![CDATA[ChaiCode]]></category><dc:creator><![CDATA[Mayank Mahajan]]></dc:creator><pubDate>Tue, 06 May 2025 08:13:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6815b2643a547c35f1fb76bb/9fcdb0c7-c85b-4f22-b16c-9f2b70c49e0b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Many developers assume that shorter code is automatically more efficient or has better time complexity. However, that's not always true—it really depends on the specific case.</p>
<p>Let’s consider a simple example of finding the heaviest ball in a basket. We’ll compare two approaches to understand how <strong>longer code can sometimes be more efficient</strong>.</p>
<h2>Case 1 – Simple and Short (Less Efficient) =&gt;</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;

using namespace std;

int main(){
 
   //To Find Max Weight

   int n = 9;
   int basket[9] = {1,1,1,1,1,2,1,1,1};
   int maxWeight = 0;
   int position;

   for(int i = 0; i&lt; n; i++){
      if(maxWeight &lt; basket[i]){
        maxWeight = basket[i];
        position = i+1;
      }
   }

   cout &lt;&lt; "Maximum Weight: " &lt;&lt; maxWeight &lt;&lt; " at position " &lt;&lt; position;
    
}
</code></pre>
<p>In Case 1, we use a simple loop to find the maximum weight in the basket. While the code is short (about 10 lines), it always runs through all <code>n</code> elements—even if the maximum is at the beginning.</p>
<h2>Case 2 – Slightly Longer (More Efficient in This Case) =&gt;</h2>
<pre><code class="language-cpp">#include &lt;iostream&gt;

using namespace std;

int main(){

int n = 9;
int basket[9] = {1,1,1,1,1,2,1,1,1};
int basketA = {basket[0] + basket[1] + basket[2]};
int basketB = {basket[3] + basket[4] + basket[5]};
int basketC = {basket[6] + basket[7] + basket[8]};
int maxWeight = 0;
int position;

if(basketA &gt; basketB){
    for(int i = 0; i &lt; 3; i++){
        if (maxWeight &lt; basket[i])
        {
            maxWeight = basket[i];
            position = i+1;
        }
        
    }
}else if(basketB &gt; basketC){
    for(int i = 3; i &lt; 6; i++){
        if (maxWeight &lt; basket[i])
        {
            maxWeight = basket[i];
            position = i+1;
        }
        
    }
}else{
    for(int i = 6; i &lt; 9; i++){
        if (maxWeight &lt; basket[i])
        {
            maxWeight = basket[i];
            position = i+1;
        }
        
    }
}
 
cout &lt;&lt; "Maximum Weight: " &lt;&lt; maxWeight &lt;&lt; " at position " &lt;&lt; position;
    
}
</code></pre>
<p>In Case 2, we divide the basket into three equal parts and compare the total weights of each part. Then, we only check the part that has the highest total weight, reducing the number of comparisons. This increases the number of lines but improves efficiency <strong>in this specific scenario</strong>, where we already know the max is likely in a heavier group.</p>
<h3>Conclusion</h3>
<p>Shorter code might seem appealing at first glance, but efficiency isn't just about line count—it's about context, logic, and intent. In real-world scenarios, a few extra lines can significantly improve performance or adaptability. Case 2 demonstrates how a longer solution can be more optimized by reducing unnecessary work. Ultimately, the best code isn't always the shortest—it's the one that balances clarity, efficiency, and problem-solving effectively.</p>
]]></content:encoded></item></channel></rss>