<?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: Dinuka Shavinda</title>
    <description>The latest articles on DEV Community by Dinuka Shavinda (@dinukashavi).</description>
    <link>https://dev.to/dinukashavi</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%2F4126995%2F4c39eeee-d0a0-412b-a18f-7374debc4277.png</url>
      <title>DEV Community: Dinuka Shavinda</title>
      <link>https://dev.to/dinukashavi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dinukashavi"/>
    <language>en</language>
    <item>
      <title>What I Actually Learned Adding Asgardeo Login to a Side Project</title>
      <dc:creator>Dinuka Shavinda</dc:creator>
      <pubDate>Tue, 15 Sep 2026 23:27:29 +0000</pubDate>
      <link>https://dev.to/dinukashavi/what-i-actually-learned-adding-asgardeo-login-to-a-side-project-26cc</link>
      <guid>https://dev.to/dinukashavi/what-i-actually-learned-adding-asgardeo-login-to-a-side-project-26cc</guid>
      <description>&lt;p&gt;I recently added Asgardeo (WSO2's identity platform) to a hotel booking app I've been building. My main reason was to see how a managed identity provider compares to the JWT/OAuth authentication I'd built by hand. I expected to write a short "here's how you add login" post. Instead I found a scope-release gotcha worth understanding, a dependency bug worth filing, and one wrong conclusion I had to walk back before publishing this.&lt;/p&gt;

&lt;p&gt;The setup&lt;/p&gt;

&lt;p&gt;The app already has its own auth: a JWT held in Redux, attached to API calls via RTK Query, with role-based route protection. I added Asgardeo as a second, parallel system, not a replacement, using &lt;a class="mentioned-user" href="https://dev.to/asgardeo"&gt;@asgardeo&lt;/a&gt;/react, wired into a protected /asgardeo route that shows the signed-in user's ID token claims. The two systems share no state; it was a deliberately additive integration, so I could compare "what I built myself" against "what a managed identity platform hands you for free" without touching what already worked.&lt;/p&gt;

&lt;p&gt;Getting sign-in and sign-out working took about twenty minutes. Understanding what happened next took a lot longer, and taught me more.&lt;/p&gt;

&lt;p&gt;Claim release isn't one setting. It's three.&lt;/p&gt;

&lt;p&gt;The first surprise: I requested the profile scope and got back an ID token with username and sub, but email, given_name, and family_name all showed up empty. My first instinct was that I'd misconfigured something. The real answer was more interesting: a claim reaching an ID token has to clear three independent gates, and an empty field looks identical no matter which gate it failed.&lt;/p&gt;

&lt;p&gt;Gate 1: the value has to exist. I'd self-registered with just an email and password, so there was no first or last name stored on the account at all. No amount of configuration releases a value that was never set.&lt;/p&gt;

&lt;p&gt;Gate 2: the attribute has to be released to the application. Under the app's settings, each attribute needs to be explicitly marked as requested. This is a separate switch from the OAuth scope. Enabling one does nothing for the other.&lt;/p&gt;

&lt;p&gt;Gate 3: the owning scope has to be requested. Claims are grouped under scopes, and the grouping isn't always where you'd guess. I checked the tenant's discovery document directly and found email listed as its own scope, entirely separate from profile. So given_name and family_name depend only on gates 1 and 2 (they live under profile, which I was already requesting). email would stay empty no matter what I did to the other two gates, because I'd never requested its scope.&lt;/p&gt;

&lt;p&gt;To actually prove this rather than just assert it, I ran a controlled test: changed exactly one variable (set a first name, then released given_name under the app's attribute settings) while leaving everything else fixed, then re-authenticated.&lt;/p&gt;

&lt;p&gt;Before:&lt;/p&gt;

&lt;p&gt;json&lt;br&gt;
{&lt;br&gt;
  "sub": "a8d1df04-3147-450f-9ebd-c3d1fccda043",&lt;br&gt;
  "username": "&lt;a href="mailto:examplemail@gmail.com"&gt;examplemail@gmail.com&lt;/a&gt;"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;After (only given_name changed on either end):&lt;/p&gt;

&lt;p&gt;json&lt;br&gt;
{&lt;br&gt;
  "given_name": "Dinuka",&lt;br&gt;
  "sub": "a8d1df04-3147-450f-9ebd-c3d1fccda043",&lt;br&gt;
  "username": "&lt;a href="mailto:examplemail@gmail.com"&gt;examplemail@gmail.com&lt;/a&gt;"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;email and family_name stayed empty, exactly as the three-gate model predicted. Changing every gate at once would have told me nothing; I'd have had three plausible explanations for one outcome. Isolating one variable is what turned a guess into a verified conclusion.&lt;/p&gt;

&lt;p&gt;The bug I found, and the conclusion I had to retract&lt;/p&gt;

&lt;p&gt;While digging into the SDK's dependency tree, I noticed &lt;a class="mentioned-user" href="https://dev.to/asgardeo"&gt;@asgardeo&lt;/a&gt;/react pins react-dom as a hard dependency at an exact version (19.x), while react is a much looser peer dependency (&amp;gt;=16.8.0). On a React 18 project, that's a contradiction npm can't resolve cleanly. It installs a second, mismatched react-dom alongside the app's own, and flags the whole tree as invalid.&lt;/p&gt;

&lt;p&gt;At first, I assumed this was the cause of a blank white screen I was seeing in the app. I built a workaround (an npm overrides entry plus a Vite dedupe config), the blank screen persisted, and I wrote up the dependency conflict as the culprit anyway. In hindsight, that persistence should have been the first red flag. A fix that doesn't fix the symptom is evidence the diagnosis is wrong, not a reason to keep the diagnosis and add more workaround.&lt;/p&gt;

&lt;p&gt;The actual cause, once I read the real browser console error instead of guessing, was a completely unrelated pre-existing bug: a component rendering a raw API error object directly as JSX, which crashes React and unmounts the whole page. Nothing to do with Asgardeo at all.&lt;/p&gt;

&lt;p&gt;To find out whether the dependency conflict was a real problem on its own merits, I built a clean, minimal reproduction: no workarounds, no other app code, just the SDK in a fresh Vite + React 18 project. It rendered fine, in both dev and production builds, with no console errors. The SDK's actual code never imports the mismatched react-dom copy, so the broken renderer just sits there unused, adding about 4KB of dead code to the bundle rather than causing any runtime failure.&lt;/p&gt;

&lt;p&gt;So the bug is real, but smaller than I first claimed: a dependency-declaration defect and a bundle-size cost, not a crash. I filed it upstream with that corrected scope, asgardeo/javascript#571, including the clean reproduction, the exact npm output showing the invalid tree, and a suggested fix (moving react-dom to peerDependencies, matching how react is already handled).&lt;/p&gt;

&lt;p&gt;What actually mattered here&lt;/p&gt;

&lt;p&gt;Neither of these findings was hard to produce once I stopped accepting the first plausible explanation. The claim-release model came from checking a real discovery document instead of guessing at scope groupings. The corrected bug report came from testing my own fix in isolation instead of trusting that a workaround I'd written must have worked. In both cases, the useful output wasn't the code. It was going back and checking whether what I believed actually matched what I could observe.&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>react</category>
      <category>security</category>
      <category>sideprojects</category>
    </item>
  </channel>
</rss>
