<?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: Syntax Surge</title>
    <description>The latest articles on DEV Community by Syntax Surge (@syntaxsurge).</description>
    <link>https://dev.to/syntaxsurge</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1793974%2Fbe9e7fec-6798-4497-8ffd-bed542f94f36.jpg</url>
      <title>DEV Community: Syntax Surge</title>
      <link>https://dev.to/syntaxsurge</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/syntaxsurge"/>
    <language>en</language>
    <item>
      <title>Viskify — Hiring on Proof, Not Promises, with Permit.io-Powered RBAC &amp; Verifiable Credentials</title>
      <dc:creator>Syntax Surge</dc:creator>
      <pubDate>Mon, 05 May 2025 11:27:36 +0000</pubDate>
      <link>https://dev.to/syntaxsurge/viskify-hiring-on-proof-not-promises-with-permitio-powered-rbac-verifiable-credentials-2gpo</link>
      <guid>https://dev.to/syntaxsurge/viskify-hiring-on-proof-not-promises-with-permitio-powered-rbac-verifiable-credentials-2gpo</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/permit_io"&gt;Permit.io Authorization Challenge&lt;/a&gt;: Permissions Redefined&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built 🛠️
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Viskify&lt;/strong&gt; is a full-stack hiring platform that lets candidates prove their skills and credentials with &lt;strong&gt;blockchain-signed Verifiable Credentials (VCs)&lt;/strong&gt; and &lt;strong&gt;AI-graded SkillPasses&lt;/strong&gt;. Recruiters can then filter talent by &lt;em&gt;proof&lt;/em&gt; instead of promises, while issuers (universities / employers) sign off on credentials in a couple of clicks.&lt;br&gt;
To keep security sane as the feature-set grows, I ripped out every hand-rolled &lt;code&gt;if (role === …)&lt;/code&gt; check and moved &lt;strong&gt;all&lt;/strong&gt; authorization logic to &lt;strong&gt;Permit.io&lt;/strong&gt;. The result is a cleaner codebase, live-editable policies, and a demo that shows how externalized RBAC can power a real production workflow (candidate → issuer → recruiter → admin) with zero redeploys.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Persona&lt;/th&gt;
&lt;th&gt;Everyday pain&lt;/th&gt;
&lt;th&gt;Viskify fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Candidate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Re-uploading the same PDF certificates to each job portal&lt;/td&gt;
&lt;td&gt;One DID-backed profile + SkillPass quizzes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Recruiter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual credential verification, résumé keyword soup&lt;/td&gt;
&lt;td&gt;Filter by on-chain VCs and quiz scores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Issuer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Endless email chains for reference checks&lt;/td&gt;
&lt;td&gt;Dashboard to review and sign requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Admin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fragile in-code ACLs&lt;/td&gt;
&lt;td&gt;Permit.io PDP enforcing policies centrally&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Demo 🌐
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://viskify-permit-io.vercel.app" rel="noopener noreferrer"&gt;https://viskify-permit-io.vercel.app&lt;/a&gt;
&lt;em&gt;Log in with the test users below or create your own account (auto-synced to Permit).&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Email&lt;/th&gt;
&lt;th&gt;Password&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;admin@test.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;myPassword&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;admin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;candidate@test.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;myPassword&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;candidate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;recruiter@test.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;myPassword&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;recruiter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;issuer@test.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;myPassword&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;issuer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Want to see Permit in action?&lt;br&gt;
&lt;em&gt;Log in as &lt;code&gt;admin@test.com&lt;/code&gt;, open *&lt;/em&gt;/dashboard*&lt;em&gt;, then hit &lt;code&gt;/api/admin/stats&lt;/code&gt; in another tab – 200 OK.&lt;br&gt;
Log out, switch to &lt;code&gt;candidate@test.com&lt;/code&gt;, hit the same endpoint – 401 Unauthorized.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Screenshots:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9euk9pg3qfan6ez44his.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9euk9pg3qfan6ez44his.png" alt="Landing page screenshot" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hy69znfvcjom19mwwyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hy69znfvcjom19mwwyl.png" alt="Admin dashboard screenshot" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwftianqxle8h027smoud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwftianqxle8h027smoud.png" alt="Admin stats API call screenshot" width="800" height="682"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1td8vo4siokylj7j6ybq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1td8vo4siokylj7j6ybq.png" alt="Permit.io audit log screenshot" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Repo 📦
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/syntaxsurge/viskify-permit-io" rel="noopener noreferrer"&gt;https://github.com/syntaxsurge/viskify-permit-io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key folders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/permit&lt;/code&gt;&lt;/strong&gt; – declarative base RBAC policy (&lt;code&gt;base.yml&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/scripts/sync-permit.ts&lt;/code&gt;&lt;/strong&gt; – idempotent seeding of roles, resources &amp;amp; demo users via the Permit SDK.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/app&lt;/code&gt;&lt;/strong&gt; – Next.js 15 App Router code (Server Actions, Route Handlers, RSC).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The README explains the full architecture plus copy-paste setup commands.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Journey 🛤️
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prototype → pain:&lt;/strong&gt; I started with classic role checks sprinkled across API routes. After adding a fourth role the code smelled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enter Permit.io:&lt;/strong&gt; Spun up the cloud PDP, wrote an RBAC policy in YAML, and deleted ~120 LOC of brittle guards. ✂️&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI FTW:&lt;/strong&gt; The &lt;strong&gt;Permit CLI&lt;/strong&gt; let me seed roles/resources and demo users straight from &lt;code&gt;pnpm permit:cli&lt;/code&gt;; no dashboard clicking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live tweaks:&lt;/strong&gt; During testing a recruiter needed read-only access to a new &lt;code&gt;talent_search&lt;/code&gt; resource. I toggled a checkbox in Permit and instantaneously the UI lit up – no redeploy, no PR.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Takeaways:&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Externalized auth turns “oops, forgot a check” bugs into policy edits.&lt;/li&gt;
&lt;li&gt;Keeping policies next to code (&lt;code&gt;/permit/*.yml&lt;/code&gt;) still feels GitOps-friendly.&lt;/li&gt;
&lt;li&gt;The PDP latency is negligible (&amp;lt;2 ms in local tests).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Using Permit.io for Authorization 🔐
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Policy definition&lt;/strong&gt; – &lt;code&gt;permit/policies/base.yml&lt;/code&gt; declares roles (&lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;candidate&lt;/code&gt;, &lt;code&gt;recruiter&lt;/code&gt;, &lt;code&gt;issuer&lt;/code&gt;), resources (&lt;code&gt;dashboard&lt;/code&gt;, &lt;code&gt;admin_stats&lt;/code&gt;, …) and actions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seeding&lt;/strong&gt; – &lt;code&gt;pnpm permit:cli&lt;/code&gt; runs &lt;code&gt;scripts/sync-permit.ts&lt;/code&gt; which:&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Creates the roles/resources via the SDK.&lt;/li&gt;
&lt;li&gt;Assigns granular permissions (only admins get &lt;code&gt;admin_stats:read&lt;/code&gt;, everyone gets &lt;code&gt;dashboard:view&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Syncs four demo users and assigns their roles.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;API routes use the same helper (&lt;code&gt;assertPermission&lt;/code&gt;) before hitting the DB.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local vs Cloud PDP&lt;/strong&gt; – by default we hit &lt;code&gt;https://cloudpdp.api.permit.io&lt;/code&gt;, but running the PDP Docker image locally (&lt;code&gt;docker run permitio/pdp-v2 ...&lt;/code&gt;) and setting &lt;code&gt;PERMIT_PDP_URL&lt;/code&gt; works identically.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Thank you to the Permit.io team &amp;amp; DEV community for a super-smooth DX and a fun challenge! 🎉
&lt;/h3&gt;

</description>
      <category>devchallenge</category>
      <category>permitchallenge</category>
      <category>webdev</category>
      <category>security</category>
    </item>
  </channel>
</rss>
