<?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: Sanu Ranjan</title>
    <description>The latest articles on DEV Community by Sanu Ranjan (@sanuranjan).</description>
    <link>https://dev.to/sanuranjan</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4004454%2Fffb35b17-265a-4a59-81f7-799f9eb14f08.png</url>
      <title>DEV Community: Sanu Ranjan</title>
      <link>https://dev.to/sanuranjan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sanuranjan"/>
    <language>en</language>
    <item>
      <title>Understanding GitHub OAuth Login</title>
      <dc:creator>Sanu Ranjan</dc:creator>
      <pubDate>Mon, 31 Aug 2026 12:10:18 +0000</pubDate>
      <link>https://dev.to/sanuranjan/understanding-github-oauth-login-1fa4</link>
      <guid>https://dev.to/sanuranjan/understanding-github-oauth-login-1fa4</guid>
      <description>&lt;h2&gt;
  
  
  Starting With Redirects
&lt;/h2&gt;

&lt;p&gt;This is a beginner friendly post.&lt;/p&gt;

&lt;p&gt;I am writing it because the GitHub OAuth login flow confused the hell out of me, and it took me a long time to work out why. The confusion was not about the code. The code is short, and you can copy it from anywhere.&lt;/p&gt;

&lt;p&gt;What frustrated me was a much simpler question that no tutorial answered directly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What request/response are being sent/received and who is sending and receiving them?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every diagram I found showed arrows going back and forth between a browser, my backend, and GitHub. I assumed my backend was talking to GitHub the whole time. It is not. Most of the time it is the browser doing the talking, and my backend is just handing it addresses to go to.&lt;/p&gt;

&lt;p&gt;And no, asking an AI did not shortcut it for me. It was frustrating and I had to dig a lot, because AI does not know what gaps we have in our knowledge and neither do we. The answers came back fluent and correct but I stayed just as confused as before.&lt;/p&gt;

&lt;p&gt;So if you are a beginner, expect to dig. Even the most advanced model will hand you a tidy summary that reads like understanding but is not one, and it will keep doing that until you learn to ask a sharper questions.&lt;/p&gt;

&lt;p&gt;Once that clicked, everything else fell into place. So we are going to go slowly, and we are going to start well before OAuth.&lt;/p&gt;




&lt;h2&gt;
  
  
  The knowledge gap (Prerequisites)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;HTTP requests and responses. You should know that a response has a status code, a set of headers, and a body, and their purpose.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What is a redirect request?(covered in the blog)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chrome DevTools, specifically the Network tab. You need to be able to open it, watch requests appear as you click around, click a single request, and read its Request Headers and Response Headers. You should also know where the "Preserve log" checkbox is, because without it the list wipes itself every time the page changes, and in this flow the page changes four times.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writing requests using the &lt;code&gt;fetch&lt;/code&gt; API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cookies, at least the basics. What are cookies, their basic working, what the &lt;code&gt;Set-Cookie&lt;/code&gt; header in a response does, that the browser stores it and attaches it to later requests automatically, and roughly what &lt;code&gt;HttpOnly&lt;/code&gt; means. The Application tab in DevTools is where you look at them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What are cross site requests, and how the browser decides what counts as one and handles it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authentication and Authorization with JWT using Express.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And the &lt;a href="https://github.com/Sanu-Ranjan/your-repo" rel="noopener noreferrer"&gt;code used&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Part 1: What a redirect actually is
&lt;/h2&gt;

&lt;p&gt;Before we touch OAuth, we need to be completely solid on one thing. Everything depends on it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The normal case first
&lt;/h3&gt;

&lt;p&gt;When our browser asks a server for a page, it gets back a response made of three parts: a status code, some headers, and a body.&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="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="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text/html&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&lt;/span&gt;Hello&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The status is &lt;code&gt;200&lt;/code&gt;, which means "here is what you asked for". The body is HTML. The browser paints that HTML on screen. The URL bar shows the address we requested. Nothing surprising.&lt;/p&gt;

&lt;h3&gt;
  
  
  Now the redirect
&lt;/h3&gt;

&lt;p&gt;A redirect is a response with a status code in the 300s, usually &lt;code&gt;302&lt;/code&gt;, and &lt;strong&gt;no body at all&lt;/strong&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="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="m"&gt;302&lt;/span&gt; &lt;span class="ne"&gt;Found&lt;/span&gt;
&lt;span class="na"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/login/oauth/authorize?client_id=abc123&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire response. There is no HTML. There is nothing for the browser to paint.&lt;/p&gt;

&lt;p&gt;What there is instead is a header called &lt;code&gt;Location&lt;/code&gt;, and its value is an address.&lt;/p&gt;

&lt;h3&gt;
  
  
  Here is the part I had wrong
&lt;/h3&gt;

&lt;p&gt;I assumed, for a long time, that the server sending this response then goes to that address itself. It does not. The server has finished the request response cycle with this response. It wrote a string into a header, set a status code, and closed the connection. It is done. It has no idea what happens next and it does not wait to find out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The browser is the one that acts.&lt;/strong&gt; It reads the &lt;code&gt;Location&lt;/code&gt; value and sends a brand new GET request to that address, entirely on its own initiative.&lt;/p&gt;

&lt;p&gt;Let us be precise about what "brand new request" means, because this is where the whole flow lives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is a separate request, with its own connection.&lt;/li&gt;
&lt;li&gt;It gets its own row in the Network tab.&lt;/li&gt;
&lt;li&gt;It carries whatever cookies belong to that new address.&lt;/li&gt;
&lt;li&gt;It is indistinguishable from the user having typed that address into the URL bar by hand.&lt;/li&gt;
&lt;li&gt;If that address also replies with a redirect, the browser does this entire dance again.&lt;/li&gt;
&lt;li&gt;And the most important thing is that the browser sends the query parameters along when it requests that address.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why you never see it happen
&lt;/h3&gt;

&lt;p&gt;Because nothing was rendered. The &lt;code&gt;302&lt;/code&gt; response had no body, so there was nothing to display, so the URL bar had no reason to update.&lt;/p&gt;

&lt;p&gt;This is why a chain of two or three redirects looks like a single instant jump on screen. There were three separate requests. We just never saw the intermediate ones, because none of them produced a page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The sentence to carry into the rest of this post:&lt;/strong&gt; a redirect response from someone is telling the browser where to go next.&lt;/p&gt;

&lt;p&gt;If you take nothing else from this article, take that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: The goal, and the flow in one pass
&lt;/h2&gt;

&lt;p&gt;Now that redirects make sense, let us look at what we are actually building.&lt;/p&gt;

&lt;p&gt;The goal is to log a user into our app &lt;strong&gt;without our app ever seeing their GitHub password&lt;/strong&gt;. They type that password on github.com and nowhere else. Our app never touches it, never sees it, so it cannot leak it.&lt;/p&gt;

&lt;p&gt;Here is the whole flow before we go into any detail. Read it once to set a context, then we will take it apart piece by piece.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user clicks a "Log in with GitHub" button, which sends the browser to a route on our own backend.&lt;/li&gt;
&lt;li&gt;Our server responds with a redirect pointing at GitHub. The browser follows it.&lt;/li&gt;
&lt;li&gt;The user logs in on github.com and approves our app.&lt;/li&gt;
&lt;li&gt;GitHub responds with a redirect pointing back at &lt;strong&gt;our&lt;/strong&gt; server. The browser follows that too. The address it points at carries a temporary &lt;code&gt;code&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Our server takes that code and calls GitHub directly, server to server, trading it for an access token.&lt;/li&gt;
&lt;li&gt;Our server uses that token to ask GitHub who this person is.&lt;/li&gt;
&lt;li&gt;Our backend saves the user, signs its own JWT, sets it in an HttpOnly cookie, and redirects to the dashboard.&lt;/li&gt;
&lt;li&gt;And now the user is logged in to our app.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now count how many of those are our server talking to GitHub over the network.&lt;/p&gt;

&lt;p&gt;Steps 5 and 6. That is it. Two requests.&lt;/p&gt;

&lt;p&gt;Steps 1 through 4 are all browser navigations, exactly like the one we described in Part 1. Our backend and GitHub are just taking turns handing addresses along with &lt;em&gt;query parameters&lt;/em&gt; to our browser through redirect requests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 3: The button, and why it points at our own server
&lt;/h2&gt;

&lt;p&gt;Our login button is an anchor tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:5000/api/auth/github"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Log in with GitHub&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one line has two decisions baked into it, and I got both of them wrong at first. Let us take them one at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Question one: why does it point at our own Express server?
&lt;/h3&gt;

&lt;p&gt;The obvious thing to do is put GitHub's authorize URL straight into that &lt;code&gt;href&lt;/code&gt;. And it would technically work. So why bounce off our own backend first?&lt;/p&gt;

&lt;p&gt;The answer is a query parameter called &lt;code&gt;state&lt;/code&gt;. We will explain why in Part 7. For now, all we need is that the &lt;code&gt;state&lt;/code&gt; must be a &lt;strong&gt;fresh random value on every single login attempt&lt;/strong&gt;, and it must be stored in an &lt;strong&gt;HttpOnly cookie&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Hold those two constraints next to the idea of hardcoding the URL in our href.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;href&lt;/code&gt; written into JSX is a fixed string. It is baked in when the app is built. Every user, on every attempt, from now until we redeploy, would send the exact same &lt;code&gt;state&lt;/code&gt;. A value that never changes is not random, so it defeats the purpose.&lt;/p&gt;

&lt;p&gt;"Fine," I thought, "I will generate it in the browser with JavaScript."&lt;/p&gt;

&lt;p&gt;But frontend JavaScript &lt;strong&gt;cannot set an HttpOnly cookie&lt;/strong&gt;. That is the entire purpose of the HttpOnly flag. It exists specifically to keep JavaScript away from the value. So we would have to store our &lt;code&gt;state&lt;/code&gt; somewhere JavaScript can reach, like localStorage, which means any cross site scripting bug in our app can read it and the protection is gone again.&lt;/p&gt;

&lt;p&gt;Let's look at what actually needs to happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a random value.&lt;/li&gt;
&lt;li&gt;Store it in an HttpOnly cookie.&lt;/li&gt;
&lt;li&gt;Send the redirect response taking the user to GitHub's login/consent page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those three things have to happen &lt;strong&gt;together, in one response&lt;/strong&gt;. Only a server can do all three. The browser can do the third one, but not the second.&lt;/p&gt;

&lt;p&gt;That is why the first stop is our own route, and GitHub comes one step later.&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;client_id&lt;/code&gt;, &lt;code&gt;scope&lt;/code&gt; and &lt;code&gt;redirect_uri&lt;/code&gt; differ between development and production, and on the server they come from environment variables. Keeping the URL construction on the backend means our frontend knows nothing about GitHub at all. That matters a lot the day we add Google login next to it, because nothing in the frontend has to change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Question two: as the href holds the backend, why an anchor tag and not a GET request using the &lt;code&gt;fetch&lt;/code&gt; API?
&lt;/h3&gt;

&lt;p&gt;Because our route is going to respond with a redirect, and we need the &lt;strong&gt;browser itself&lt;/strong&gt; to act on it.&lt;/p&gt;

&lt;p&gt;An anchor tag hands control to the browser, and the browser makes the GET request and handles the redirect response received. That is what makes the rest of the chain happen at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 4: What our login route does
&lt;/h2&gt;

&lt;p&gt;Three things, in a single response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/auth/github&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomBytes&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="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;oauth_state&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;httpOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sameSite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lax&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maxAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:5000/api/auth/github/callback&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user:email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://github.com/login/oauth/authorize?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read &lt;code&gt;res.redirect()&lt;/code&gt; carefully, because the name invites the wrong mental model.&lt;/p&gt;

&lt;p&gt;It is &lt;strong&gt;not&lt;/strong&gt; a function that reaches out to GitHub. It does not open a connection. It does not wait for anything. All it does is set a &lt;code&gt;302&lt;/code&gt; status and a &lt;code&gt;Location&lt;/code&gt; header on our response, and end it. Whether anything happens after that is entirely the browser's business.&lt;/p&gt;

&lt;p&gt;Two things to notice in the params.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;client_id&lt;/code&gt; is public. It sits in a URL. That is fine and by design.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;client_secret&lt;/code&gt; does not appear here at all. It never goes near the browser. We will use it exactly once, in Part 8, from our server.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 5: The user is on GitHub now
&lt;/h2&gt;

&lt;p&gt;The browser follows the redirect and lands on github.com.&lt;/p&gt;

&lt;p&gt;Before showing anything, GitHub checks two things. That our &lt;code&gt;client_id&lt;/code&gt; exists, and that the &lt;code&gt;redirect_uri&lt;/code&gt; we sent is an &lt;strong&gt;exact string match&lt;/strong&gt; for the one registered in our OAuth app settings. Not a close match but exact. One trailing slash difference and we get an error page instead of a login screen.&lt;/p&gt;

&lt;p&gt;Then the user types their password.&lt;/p&gt;

&lt;p&gt;This happens on github.com, on a page GitHub served, over GitHub's own TLS connection. Our app has no visibility into any part of it. There is no way for us to read that password even if we wanted to, because it is never sent anywhere near us.&lt;/p&gt;

&lt;p&gt;That is the entire security promise of OAuth, and it is worth pausing on. The reason this whole convoluted redirect dance exists is to get that one property.&lt;/p&gt;

&lt;p&gt;After the password, GitHub shows the consent screen listing the scopes we asked for. The user approves.&lt;/p&gt;

&lt;p&gt;Consent only appears the first time. GitHub records that this user authorized this app for these scopes. On every later login it skips the screen entirely and redirects straight back, which is why the second login feels fast.&lt;/p&gt;

&lt;p&gt;It reappears in two cases. If we add a scope the user has not approved, or if the user revokes our app at github.com/settings/applications. That second one is worth doing once during development just to see the screen again.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 6: The "callback URL" is a misleading name
&lt;/h2&gt;

&lt;p&gt;This was the single biggest source of my confusion, so I want to be blunt about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub does not call our callback URL.&lt;/strong&gt; It never opens a connection to our server. Not once, at any point in the entire flow.&lt;/p&gt;

&lt;p&gt;What GitHub actually does is send another &lt;code&gt;302&lt;/code&gt; back to the browser:&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="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="m"&gt;302&lt;/span&gt; &lt;span class="ne"&gt;Found&lt;/span&gt;
&lt;span class="na"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:5000/api/auth/github/callback?code=abc123&amp;amp;state=xyz789&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then it hangs up. Same as Part 1. It set a status, wrote a header, and finished.&lt;/p&gt;

&lt;p&gt;The browser reads that &lt;code&gt;Location&lt;/code&gt; value and sends a fresh GET request to our server, carrying the &lt;code&gt;code&lt;/code&gt; in the query string. The browser is the middle man.&lt;/p&gt;

&lt;p&gt;So "callback URL" is the address we want the browser sent back to with the code in the query string.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 7: The state check, and a SameSite trap
&lt;/h2&gt;

&lt;p&gt;Our callback route runs. The first thing it does is compare the &lt;code&gt;state&lt;/code&gt; in the query string against the &lt;code&gt;oauth_state&lt;/code&gt; cookie, and then clear that cookie.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why?
&lt;/h3&gt;

&lt;p&gt;Here is the attack it prevents. Follow it slowly, because it is easy to misread which account is which.&lt;/p&gt;

&lt;p&gt;An attacker goes to GitHub and authorizes our app &lt;strong&gt;using his own GitHub account&lt;/strong&gt;. He gets back a valid &lt;code&gt;code&lt;/code&gt;. It is a real code, correctly issued, and it points at his account.&lt;/p&gt;

&lt;p&gt;He does not use it.&lt;/p&gt;

&lt;p&gt;Instead, he gets our user to load this address, maybe through a link in an email or an image tag on a page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://ourapp.com/api/auth/github/callback?code=HIS_CODE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our server sees a code, redeems it, and logs that user's browser into &lt;strong&gt;the attacker's&lt;/strong&gt; account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wait, why would he use his own account?
&lt;/h3&gt;

&lt;p&gt;This is the part that confused me, and it is worth stopping on, because the attack looks backwards at first.&lt;/p&gt;

&lt;p&gt;He is not trying to break into the victim's GitHub. He cannot. The code he planted was issued for his account, so our server reads his email, his profile, his numeric id, and logs the browser into his user record. The victim's GitHub is never touched at any point, and the user has no clue because there was no consent screen.&lt;/p&gt;

&lt;p&gt;What he gets is everything the victim does &lt;strong&gt;afterwards&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The victim clicks the link and our app looks completely normal. They are logged in, nothing errors, and almost nobody checks which account they are in. So they carry on and use the app. They write notes, upload files, save a delivery address, connect something at checkout. All of it gets written against the attacker's user record.&lt;/p&gt;

&lt;p&gt;Then he logs into our app from his own machine, normally, with his own GitHub, and reads it.&lt;/p&gt;

&lt;p&gt;So the value of this attack depends entirely on what our app stores. On a login only app that holds nothing, it is close to worthless. On anything where users create, upload or save things, it is a real problem. The account is a container, and he has quietly handed the victim one that he owns.&lt;/p&gt;

&lt;h3&gt;
  
  
  How &lt;code&gt;state&lt;/code&gt; stops it
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;state&lt;/code&gt; cookie shuts this down cleanly. The attacker cannot read or set an HttpOnly cookie in someone else's browser. So his forged request arrives with a &lt;code&gt;state&lt;/code&gt; that does not match whatever is in the victim's cookie, and our server rejects it before ever touching the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  The SameSite trap
&lt;/h3&gt;

&lt;p&gt;This one is confusing as hell.&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;oauth_state&lt;/code&gt; cookie belongs to our own domain. So I expected it to be sent to our own server without question. That is not how SameSite works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SameSite is not about who owns the cookie. It is about which page the navigation started on.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Look back at Part 6. That redirect originates on github.com. So from the browser's point of view this is a cross site navigation, even though the destination is ours and the cookie is ours.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;SameSite=Strict&lt;/code&gt;, the browser withholds the cookie. Our state check then fails on every single login, and the error message will not tell us why, because from our code's perspective the cookie simply is not there.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SameSite=Lax&lt;/code&gt; is the minimum that works, because Lax permits cookies on top level navigations like this one.&lt;/p&gt;

&lt;p&gt;We want &lt;code&gt;Lax&lt;/code&gt; on our own session JWT too, for two separate reasons. First, the page load after login is the tail end of a chain that started on github.com. Second, if a user clicks a link to our site from Gmail or WhatsApp, &lt;code&gt;Strict&lt;/code&gt; would land them on our page logged out, which looks like a bug to them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 8: Trading the code for a token
&lt;/h2&gt;

&lt;p&gt;This is the first time in the entire flow that our server talks to GitHub over the network. No browser is involved. Nothing here is visible to the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/login/oauth/access_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:5000/api/auth/github/callback&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things here will waste an hour if nobody warns you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Send &lt;code&gt;Accept: application/json&lt;/code&gt;.&lt;/strong&gt; Without it, GitHub replies with a form encoded string that looks like &lt;code&gt;access_token=gho_xxx&amp;amp;scope=&amp;amp;token_type=bearer&lt;/code&gt;. Your &lt;code&gt;tokenRes.json()&lt;/code&gt; will throw, and the error will not point at the missing header.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A bad code returns HTTP 200.&lt;/strong&gt; Not 400. Not 401. You get a &lt;code&gt;200 OK&lt;/code&gt; with an error object sitting in the body. So &lt;code&gt;if (!tokenRes.ok)&lt;/code&gt; will happily let a failure through. You have to check whether &lt;code&gt;access_token&lt;/code&gt; is actually present in the parsed response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is it safe for the code to travel through the URL?
&lt;/h3&gt;

&lt;p&gt;This bothered me for a while. In Part 6 that code sat in the URL in plain text. Which means it went into browser history.&lt;/p&gt;

&lt;p&gt;It is fine, because the code is &lt;strong&gt;single use and short lived&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The moment we exchange it, GitHub marks it consumed. Send the same code again and we get an error. And it expires on its own in about ten minutes whether we use it or not.&lt;/p&gt;

&lt;p&gt;So anyone who digs that string out is holding something that has either already been spent or is about to expire on its own. The reusable, valuable thing is the access token we just received, and that never leaves our server.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 9: Finding out who they are
&lt;/h2&gt;

&lt;p&gt;Here is a thing that is easy to assume and wrong: the access token does not tell us who the user is.&lt;/p&gt;

&lt;p&gt;It is a key, not an ID card. It is proof that we are allowed to ask questions about the user logging in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User-Agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-app-name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/vnd.github+json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;That &lt;code&gt;User-Agent&lt;/code&gt; header is not optional.&lt;/strong&gt; GitHub's API returns &lt;code&gt;403 Forbidden&lt;/code&gt; without it, and the error message does not obviously point at the cause. Every GitHub API request needs it.&lt;/p&gt;

&lt;p&gt;Now we make our second and last server to server call.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://api.github.com/user&lt;/code&gt; gives us the profile: &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;login&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;avatar_url&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;email&lt;/code&gt; will very often come back as &lt;code&gt;null&lt;/code&gt;, which is not a bug. Most people keep their email private on GitHub, and that endpoint respects the setting.&lt;/p&gt;

&lt;p&gt;So we call &lt;code&gt;https://api.github.com/user/emails&lt;/code&gt; as well. That returns an array of every email on the account, and we want the one where &lt;strong&gt;both&lt;/strong&gt; &lt;code&gt;primary&lt;/code&gt; and &lt;code&gt;verified&lt;/code&gt; are &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;verified&lt;/code&gt; part is not optional either. If the primary email is not verified, we treat the account as unverified rather than trusting the address. Otherwise someone could add an email they do not own to their GitHub account, log into our app, and get matched to an existing user in our database who does own it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 10: Saving them, and our own session
&lt;/h2&gt;

&lt;p&gt;Now that we have our user details, we set our own JWT using them as the payload and issue that to the client after login.&lt;/p&gt;




&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;If we remember one thing from all of this, it is this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Almost every step in OAuth is a server writing an address into a &lt;code&gt;Location&lt;/code&gt; header and hanging up, while the browser does the walking.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So here is the exercise that taught me more than every diagram I looked at. Open DevTools. Go to the Network tab. Tick "Preserve log" so the rows survive all the page changes. Clear the list. Then run a login.&lt;/p&gt;

&lt;p&gt;Go through them one at a time and find out, who sent this request, our server or the browser.&lt;/p&gt;

&lt;p&gt;P.S. I wrote this while learning it, which means there is a decent chance something here is wrong or oversimplified. If you know OAuth properly and spot a mistake, please comment. Corrections are welcomed as the post gets better for the next beginner who lands on it.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>githuboauth</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How ChatGPT Understands Your Questions?</title>
      <dc:creator>Sanu Ranjan</dc:creator>
      <pubDate>Wed, 01 Jul 2026 18:28:45 +0000</pubDate>
      <link>https://dev.to/sanuranjan/how-chatgpt-understands-your-questions-1l4h</link>
      <guid>https://dev.to/sanuranjan/how-chatgpt-understands-your-questions-1l4h</guid>
      <description>&lt;p&gt;"My cat cant read this"&lt;/p&gt;

&lt;p&gt;Held my laptop up, my cat stared at the screen for two seconds, then walked off like I'd personally offended her. Zero understanding. Fair enough, she's a cat.&lt;/p&gt;

&lt;p&gt;But here's the thing that actually gets interesting, I can't fully explain how I read either. I'm doing it right now, obviously, but no neuroscientist on the planet can fully explain how my brain turns these squiggles into "oh, a cat joke." That one's still unsolved.&lt;/p&gt;

&lt;p&gt;ChatGPT though, we can actually try to understand, not every step of it start to finish but can have an over all idea, no mystery here. So let's go through it properly, follow one sentence on its entire journey, from raw text to a reply showing up on our screen.&lt;/p&gt;

&lt;p&gt;You type this into ChatGPT:&lt;/p&gt;

&lt;p&gt;"My cat cant read this"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8kq6nj66i7kds6v7b36c.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8kq6nj66i7kds6v7b36c.png" alt=" " width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...and it immediately starts judging you(pun intended)&lt;/p&gt;

&lt;p&gt;So how did it know? Let's actually go underneath the chat window, because that's the whole point of this post, not what ChatGPT is, but what it's actually doing to your sentence in the half second before it replies.&lt;/p&gt;

&lt;p&gt;Underneath, it's a model doing exactly one job, look at some text, guess what word comes next. That's genuinely it. There's no separate grammar checker bolted on, no dictionary it's flipping through. Catching your missing apostrophe, understanding what you meant, replying like a smug know it all, all of that comes out of "guess the next word," just done at a scale that's hard to picture. Let's watch it happen on your actual sentence.&lt;/p&gt;

&lt;p&gt;Step 1: Chopping the sentence up (Tokenization)&lt;/p&gt;

&lt;p&gt;Computers can't read English, not even a little. All they work with is numbers. So before anything else happens, your sentence gets cut into small pieces called  tokens , and each piece gets a number.&lt;/p&gt;

&lt;p&gt;My cat cant read this → [My] [cat] [can] [t] [read] [this]&lt;/p&gt;

&lt;p&gt;"cant" gets split weird, into [can] and [t]. That's because "cant" isn't actually a real word, it's missing its apostrophe, so the tokenizer, which only knows the chunks it was trained on(its vocabulary), does its best and breaks it apart. Every token maps to a fixed number, and that mapping doesn't change based on our intended meaning.&lt;/p&gt;

&lt;p&gt;Step 2: Vector Embeddings&lt;/p&gt;

&lt;p&gt;A number alone tells you nothing. 47 doesn't mean anything about "cat". So each token's number gets swapped for something bigger, a long list of numbers called an  embedding , which works like a personality profile for that word. Words with related meaning end up with similar looking profiles. "cat" and "dog" sit close together numerically. "cat" and "computer" don't.&lt;/p&gt;

&lt;p&gt;These profiles aren't invented fresh for our sentence. They're premade. Learned once during training, and just sitting there in a giant table afterward we can visualize it as words positioned in 3d space such that the distance between words represent how closely they are related. When our sentence comes in, the model isn't building a personality for "cat" on the spot, it's pulling "cat"'s profile off the shelf, already fully formed from having seen the word a billion times before during training.&lt;/p&gt;

&lt;p&gt;Step 3: Remembering the order (Positional Encoding)&lt;/p&gt;

&lt;p&gt;Small problem, the next step processes every token at once, together, not one after another. Which is fast, but it also means word order gets lost by default. "My cat cant read this" and "this cant read my cat" would otherwise look the same to it.&lt;/p&gt;

&lt;p&gt;So before that step, each token gets a small stamp added to its profile, basically marking "I was word 1," "I was word 3," and so on, so the order survives even though everything's being processed together.&lt;/p&gt;

&lt;p&gt;Step 4: Where the actual understanding happens (Self Attention)&lt;/p&gt;

&lt;p&gt;Quick reminder before this part, by now every token isn't a word anymore, it's the number profile from Step 2, already stamped with its position from Step 3. That stamp matters here because attention checks every token against every other token all at once, not in order, so without it, the model would have no way to tell "My cat cant read this" apart from "this cant read my cat", same words, same profiles, totally different meaning.&lt;/p&gt;

&lt;p&gt;When "cant" "looks at" every other word, it's doing a direct number comparison, checking how closely its numbers align with "cat"'s, "read"'s, and "My"'s. The closer the alignment, the more of that word's numbers get mixed into "cant". These alignments come from patterns the model picked up during training, but here's the honest part, we can't actually read those numbers and say "this is the grammar bit" or "this is the meaning bit." Nobody can point at them and narrate what each one means in plain English. What we can say is what happens mechanically, closely aligned words get blended in heavily, weakly aligned ones barely at all, and this runs for every token at once, all comparing against each other simultaneously. That's also why it doesn't lose track in long sentences, word 2 and word 200 can compare directly in one step, instead of that information passing hand to hand through every word in between.&lt;/p&gt;

&lt;p&gt;Step 5: A private pass (Feed Forward Layer)&lt;/p&gt;

&lt;p&gt;After the mixing in Step 4, each token's profile is just a blend of other tokens' numbers. This step takes that blend and reworks it through another formula, also learned during training, using only what this one token already has, no borrowing from others this time. As for what this reworking actually captures, same honest answer as before, we can't point at it and say in plain words what it's doing. What we can say is the shape of it, Steps 4 and 5 together repeat many times, stacked on top of each other, and across all those rounds each token's profile gradually shifts from a generic word into a word shaped by this specific sentence. The exact thing each round contributes isn't something anyone can narrate, but the end result is that every token's numbers end up reflecting the whole sentence around it, not just the word on its own.&lt;/p&gt;

&lt;p&gt;Step 6: Actually picking a word (Softmax)&lt;/p&gt;

&lt;p&gt;After all those rounds of Steps 4 and 5, the model has one final set of numbers for the position where the next word should go. Now it has to turn that into an actual word. It compares those numbers against every word in its vocabulary, tens of thousands of them, and produces a raw score for each one, how well each word fits given everything so far. Those raw scores are messy and hard to work with, some could even be negative, so they get run through softmax, which just squashes them all into clean percentages that add up to 100%. Something like "You" at 38%, "That" at 20%, "Well" at 9%, and so on down the list.&lt;/p&gt;

&lt;p&gt;Then it picks a word from that list, usually leaning toward the higher percentages, adds it to the sentence, and runs the entire thing again from the start, tokenizing, embedding, comparing, reworking, all of it, to figure out the next word after that. One word at a time, which is why you watch the reply type itself out instead of appearing all at once. It's not being revealed to you from somewhere, it's being built, live, one word at a time, right in front of you.&lt;/p&gt;

&lt;p&gt;Quick recap since this is the part that trips people up, the token list, the embedding profiles, and all the internal formulas are fixed. Learned once during training, frozen after that. None of it changes while you are chatting, no matter what you type. And to answer the obvious question, no, the model isn't learning from your conversation as you go. Your chats may still be collected and used to help train future versions, depending on the service and your settings, but even then that training happens later, offline, not while you type. It does improve over time, but only when the company trains and releases a newer version, and the version you're talking to right now stays exactly the same its whole life until it's swapped for a new one. What's fresh every single time is the computation itself, your specific words getting run through those frozen numbers in a way that's never happened before, because your exact may sentence have never existed before either.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>JavaScript Closures: How They Actually Work</title>
      <dc:creator>Sanu Ranjan</dc:creator>
      <pubDate>Fri, 26 Jun 2026 21:13:35 +0000</pubDate>
      <link>https://dev.to/sanuranjan/javascript-closures-how-they-actually-work-376g</link>
      <guid>https://dev.to/sanuranjan/javascript-closures-how-they-actually-work-376g</guid>
      <description>&lt;p&gt;&lt;em&gt;A quick note before we start: this post is for beginners trying to understand how closures actually work. I have done my best as per my limited knowledge to explain what happens under the hood, so if I have gotten anything wrong, please correct me in the comments.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When I was first learning closures, I could recite the definition but still could not explain why they actually work. Information that goes past the definition and into the "why" and "How" is sometimes harder to find than expected. One of my dear friends finally explained it to me in a way that made it click, so I thought I should pass it on for any beginner going through the same thing.&lt;/p&gt;

&lt;p&gt;The definition we have probably heard: an inner function remembers variables from its outer function, even after the outer function has finished running. That is correct, but it is only the surface. The real question is, what is so special here? Why does that memory survive at all? The answer lives in how the JavaScript engine handles memory behind the scenes.&lt;/p&gt;

&lt;p&gt;Here is the example we will use the whole way through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;innerCounter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&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="nx"&gt;innerCounter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;myCounter1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;myCounter1&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;span class="nf"&gt;myCounter1&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 2&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;myCounter2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;myCounter2&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 3&lt;/span&gt;
&lt;span class="nf"&gt;myCounter1&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm using &lt;code&gt;var&lt;/code&gt; here just to make the breakdown easier to visualize.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Engine Runs the Code
&lt;/h2&gt;

&lt;p&gt;Contrary to the popular belief that JavaScript is just a simple, line-by-line interpreted language, the engine handles the code in distinct steps. There are two phases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Compilation Phase:&lt;/strong&gt; the engine scans the code and registers all variable declarations and function definitions before anything runs. For each function, it creates a function object in memory, and the function's name points to that object.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Execution Phase:&lt;/strong&gt; the code finally runs, line by line.&lt;/p&gt;

&lt;p&gt;Separately from these, there's garbage collection: the cleanup process that runs in the background. Normally, once a function finishes executing, its local execution scope is freed from memory so it doesn't waste space. With a closure, that cleanup gets blocked. The executed function's local execution scope persists, letting the inner function reach back up the scope chain and grab what it needs.&lt;/p&gt;

&lt;p&gt;Quick recap of the example above: &lt;code&gt;counter&lt;/code&gt; takes a parameter &lt;code&gt;n&lt;/code&gt; (default 0), stores it in &lt;code&gt;count&lt;/code&gt;, and returns an inner function &lt;code&gt;innerCounter&lt;/code&gt; that increments and logs &lt;code&gt;count&lt;/code&gt;. We create &lt;code&gt;myCounter1&lt;/code&gt; from &lt;code&gt;counter()&lt;/code&gt; and &lt;code&gt;myCounter2&lt;/code&gt; from &lt;code&gt;counter(a)&lt;/code&gt;, and the outputs come out as 1, 2, 3, 3. Now let's see how.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Mapping the Global Scope
&lt;/h2&gt;

&lt;p&gt;Before a single line runs, JavaScript sets up the global scope in memory during compilation, registering the names &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;myCounter1&lt;/code&gt;, and &lt;code&gt;myCounter2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When it hits the &lt;code&gt;counter&lt;/code&gt; definition, it creates a function object for it in memory, and the name &lt;code&gt;counter&lt;/code&gt; points to that object. Here's the secret sauce: every function gets a hidden internal property (often written &lt;code&gt;[[Scope]]&lt;/code&gt;) that points back to the environment where it was born. For &lt;code&gt;counter&lt;/code&gt;, that points to the global scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Creating myCounter1
&lt;/h2&gt;

&lt;p&gt;Now execution starts. It sets &lt;code&gt;a = 2&lt;/code&gt;, then calls &lt;code&gt;counter()&lt;/code&gt; with no argument.&lt;/p&gt;

&lt;p&gt;Invoking the function spins up a brand new local execution context, which points back to wherever its function definition's &lt;code&gt;[[Scope]]&lt;/code&gt; is pointing to. Inside this temporary space, a mini-compilation happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;n&lt;/code&gt; defaults to 0.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;count&lt;/code&gt; is registered and initialized to 0.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;innerCounter&lt;/code&gt; function is defined and stored and &lt;code&gt;innerCounter&lt;/code&gt; holds reference to its function definition, and its hidden &lt;code&gt;[[Scope]]&lt;/code&gt; points right back to this local scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, &lt;code&gt;counter()&lt;/code&gt; returns &lt;code&gt;innerCounter&lt;/code&gt;, which returns the reference of its function definition and we store it in &lt;code&gt;myCounter1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Normally the local execution scope of &lt;code&gt;counter()&lt;/code&gt; would now be wiped by the garbage collector. But JavaScript has a golden rule: if an environment can still be reached from the global scope, it can't be garbage collected. Since &lt;code&gt;myCounter1&lt;/code&gt; points to the inner function's definition, and the inner function points back to this local scope &lt;code&gt;counter()&lt;/code&gt;, a bridge remains. The memory survives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Climbing the Scope Chain
&lt;/h2&gt;

&lt;p&gt;Now we call &lt;code&gt;myCounter1()&lt;/code&gt;. A fresh local execution scope is created for the function execution which points back to wherever its &lt;code&gt;[[Scope]]&lt;/code&gt; is pointing to, here it points to &lt;code&gt;counter()&lt;/code&gt;'s local execution scope which is still in memory.&lt;/p&gt;

&lt;p&gt;Then in execution phase for this function it encounters &lt;code&gt;count++&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First it checks its own local context for &lt;code&gt;count&lt;/code&gt;. Nothing there. So it follows up its scope chain into the preserved parent memory the &lt;code&gt;counter()&lt;/code&gt; execution context, finds &lt;code&gt;count&lt;/code&gt; at 0, increments it to 1 in that parent scope, and logs 1, and then &lt;code&gt;myCounter1()&lt;/code&gt; local execution context is garbage collected.&lt;/p&gt;

&lt;p&gt;The second call &lt;code&gt;myCounter1()&lt;/code&gt; repeats exactly: it looks locally, finds nothing, climbs the chain, finds &lt;code&gt;count&lt;/code&gt; now at 1, increments it to 2, and logs 2.&lt;/p&gt;

&lt;p&gt;We get updated value here all because &lt;code&gt;counter()&lt;/code&gt;'s local execution scope is still not garbage collected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Total Isolation with myCounter2
&lt;/h2&gt;

&lt;p&gt;What happens when we create &lt;code&gt;myCounter2&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Because we invoke &lt;code&gt;counter&lt;/code&gt; fresh, JavaScript generates a completely separate, second local execution context. Here &lt;code&gt;n&lt;/code&gt; receives the value of &lt;code&gt;a&lt;/code&gt; (which is 2), so &lt;code&gt;count&lt;/code&gt; starts at 2.&lt;/p&gt;

&lt;p&gt;Calling &lt;code&gt;myCounter2()&lt;/code&gt; climbs its own scope chain, finds its own &lt;code&gt;count&lt;/code&gt; at 2, increments it, and outputs 3.&lt;/p&gt;

&lt;p&gt;To prove the two environments share nothing, look at the final &lt;code&gt;myCounter1()&lt;/code&gt;. It ignores whatever &lt;code&gt;myCounter2&lt;/code&gt; did, goes right back to its original environment, finds its old value of 2, increments it to 3, and logs 3.&lt;/p&gt;

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

&lt;p&gt;At the end of the day, that's all a closure is. The outer function's local execution scope escapes garbage collection because the inner function's definition, whose reference is present in the global scope, keeps a live, reachable path back to the outer function's local execution scope via &lt;code&gt;[[Scope]]&lt;/code&gt;. That persistent connection is what keeps the data alive across separate instances.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
