<?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: Tamizh</title>
    <description>The latest articles on DEV Community by Tamizh (@tamizhme).</description>
    <link>https://dev.to/tamizhme</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%2F1320145%2Ffac93a43-e4bc-488e-a927-5a6c200c8816.jpeg</url>
      <title>DEV Community: Tamizh</title>
      <link>https://dev.to/tamizhme</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tamizhme"/>
    <language>en</language>
    <item>
      <title>ZOQ Agent: Universal AI-Powered Outreach &amp; Intelligence System 🎯</title>
      <dc:creator>Tamizh</dc:creator>
      <pubDate>Mon, 26 May 2025 06:59:07 +0000</pubDate>
      <link>https://dev.to/tamizhme/zoq-agent-universal-ai-powered-outreach-intelligence-system-4e1c</link>
      <guid>https://dev.to/tamizhme/zoq-agent-universal-ai-powered-outreach-intelligence-system-4e1c</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/brightdata-2025-05-07"&gt;Bright Data AI Web Access Hackathon&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  ZOQ Agent: Universal AI-Powered Outreach &amp;amp; Intelligence System 🎯
&lt;/h1&gt;

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

&lt;p&gt;&lt;strong&gt;ZOQ Agent transforms any query into actionable outreach in under 60 seconds.&lt;/strong&gt; Simply tell it what you want and provide your context - it finds the right people, researches them in real-time, and writes hyper-personalized messages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Capability&lt;/strong&gt;: Universal query → intelligent outreach pipeline that works for sales, hiring, partnerships, feedback collection, and more.&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%2Flam1a0tk6q9zplgi0esz.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%2Flam1a0tk6q9zplgi0esz.png" alt="Image description" width="800" height="546"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Examples in Action&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🚀 &lt;strong&gt;Sales&lt;/strong&gt;: "Find AI startup founders in Bangalore" → Discovers prospects → Writes personalized cold emails mentioning their recent funding/product launches&lt;/li&gt;
&lt;li&gt;💼 &lt;strong&gt;Hiring&lt;/strong&gt;: "Find senior React developers at YC companies" → Identifies candidates → Crafts recruitment messages referencing their specific projects&lt;/li&gt;
&lt;li&gt;🤝 &lt;strong&gt;Partnerships&lt;/strong&gt;: "Find CTOs building developer tools" → Locates decision makers → Creates collaboration proposals based on their tech stack&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Feedback&lt;/strong&gt;: "Find AI agent product users" → Discovers early adopters → Writes feedback requests mentioning their specific use cases&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;&lt;a href="https://zoq-agent-production.up.railway.app/" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;/strong&gt; | 🔗 &lt;strong&gt;&lt;a href="https://github.com/0xtamizh/zoq-agent" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/strong&gt; | 🎥 &lt;strong&gt;&lt;a href="https://zoq.0xtamizh.com/demo" rel="noopener noreferrer"&gt;Demo Video&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real Execution Flow&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Query&lt;/strong&gt;: "Find AI startup founders who raised funding in 2024 - pitch our sales automation tool"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discovery&lt;/strong&gt;: Searches across multiple sources, finds 3 matching founders&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enrichment&lt;/strong&gt;: Scrapes LinkedIn profiles, company websites, recent news about their funding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email Generation&lt;/strong&gt;: Creates personalized emails like:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hey [Name],

Congrats on the $2M Series A for [Company]! Saw your interview about scaling challenges.

At ZOQ, we automate the entire sales pipeline - exactly what you need while scaling from 5 to 50 customers.

We helped [Similar Company] book $50K pipeline in 6 weeks, all automated.

Worth a 15-min chat about handling your sales while you focus on product?

Best,
[User]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;: 3 hyper-personalized emails generated in 45 seconds with 90%+ personalization accuracy.&lt;/p&gt;
&lt;h2&gt;
  
  
  How I Used Bright Data's Infrastructure
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ZOQ Agent leverages ALL 4 Bright Data MCP capabilities in a sophisticated multi-agent orchestration&lt;/strong&gt;:&lt;/p&gt;
&lt;h3&gt;
  
  
  🔍 &lt;strong&gt;Discover&lt;/strong&gt;: Multi-Source Intelligence Gathering
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool&lt;/strong&gt;: &lt;code&gt;search_engine&lt;/code&gt; via Bright Data MCP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usage&lt;/strong&gt;: Executes 8-10 parallel searches per query to find prospects across Google, LinkedIn, company databases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart Queries&lt;/strong&gt;: AI generates diverse search strategies (location-based, role-based, company-based, industry-based)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  🌐 &lt;strong&gt;Access&lt;/strong&gt;: Complex Site Navigation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt;: &lt;code&gt;web_data_linkedin_person_profile&lt;/code&gt;, &lt;code&gt;web_data_linkedin_company_profile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Challenge Solved&lt;/strong&gt;: Accesses LinkedIn profiles and company pages that require authentication and complex navigation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result&lt;/strong&gt;: Successfully extracts profile data from protected professional networks&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  📊 &lt;strong&gt;Extract&lt;/strong&gt;: Structured Real-Time Data
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt;: &lt;code&gt;scrape_as_markdown&lt;/code&gt; for company websites, &lt;code&gt;search_engine&lt;/code&gt; for news/funding data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Extracted&lt;/strong&gt;: Recent activities, funding rounds, product launches, team changes, pain points, tech stack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure&lt;/strong&gt;: AI converts raw web data into structured JSON with email signals, personalization hooks, and opportunity insights&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  ⚡ &lt;strong&gt;Interact&lt;/strong&gt;: Dynamic Content Handling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Implicit Usage&lt;/strong&gt;: Bright Data tools handle JavaScript-heavy sites, dynamic loading, and anti-bot measures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sites Handled&lt;/strong&gt;: LinkedIn (complex auth), modern company websites (SPA frameworks), news sites (dynamic content)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result&lt;/strong&gt;: Reliable data extraction from modern web applications that traditional scraping can't handle&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Multi-Agent Architecture&lt;/strong&gt;:
&lt;/h3&gt;

&lt;p&gt;User Query → Planning Agent → Discovery Agent (search_engine)&lt;br&gt;
→ Enrichment Agent (LinkedIn + website scraping)&lt;br&gt;
→ Email Writing Agent → Personalized Results&lt;/p&gt;

&lt;p&gt;Agent Action Panel&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%2Fafrx0dpzm41294cfov0g.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%2Fafrx0dpzm41294cfov0g.png" alt="Image description" width="800" height="591"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Prospect Card with personalized email&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%2Fxz7ug8ab9cg9gvme8u25.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%2Fxz7ug8ab9cg9gvme8u25.png" alt="Image description" width="800" height="787"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each agent uses Bright Data tools intelligently based on AI decision-making, creating a fully autonomous research and outreach system.&lt;/p&gt;
&lt;h2&gt;
  
  
  Performance Improvements
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Real-time web data access transforms AI performance from generic to hyper-personalized&lt;/strong&gt;:&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Before Bright Data&lt;/strong&gt; (Traditional AI):
&lt;/h3&gt;

&lt;p&gt;❌ Generic Email Template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Hi [Name], I hope this email finds you well.
I wanted to reach out about our product..."
- **Personalization**: 20% (name/company only)
- **Response Rate**: ~2-3%
- **Research Time**: Manual, hours per prospect
- **Data Freshness**: Outdated, static information
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;After Bright Data&lt;/strong&gt; (ZOQ Agent):
&lt;/h3&gt;

&lt;p&gt;✅ Hyper-Personalized Email:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Hey Alex, Congrats on Buildspace S5 Demo Day last week!
DashChat's text-to-SQL interface is brilliant.

Building + validating + finding users solo is brutal - I've been there.

At ZOQ, we automate your entire user acquisition while you iterate on product.
We helped techcorp book $90K pipeline in 6 weeks.

Worth a chat about getting you 50+ beta testers this month?"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Measurable Improvements&lt;/strong&gt;:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Personalization Accuracy&lt;/strong&gt;: 90%+ (mentions specific recent activities)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Research Speed&lt;/strong&gt;: 45 seconds vs 2+ hours manual research&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Freshness&lt;/strong&gt;: Real-time (funding announcements, product launches, recent posts)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contextual Relevance&lt;/strong&gt;: AI references specific pain points, recent events, company news&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: 100+ prospects/day vs 5-10 manual research&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Real-Time Data Advantage Examples&lt;/strong&gt;:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Funding News&lt;/strong&gt;: "Congrats on your $2M Series A announced yesterday"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product Launches&lt;/strong&gt;: "Saw your ProductHunt launch hit #3 this week"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team Updates&lt;/strong&gt;: "Noticed you're hiring 5 engineers - scaling fast!"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Industry Events&lt;/strong&gt;: "Great talk at TechCrunch Disrupt on AI automation"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;ROI Impact&lt;/strong&gt;:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time Savings&lt;/strong&gt;: 95% reduction in research time (2 hours → 45 seconds)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response Rate&lt;/strong&gt;: 3x improvement with personalized hooks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline Quality&lt;/strong&gt;: Higher conversion due to relevant, timely outreach&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: 20x more prospects reached with same effort&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Key Differentiator&lt;/strong&gt;: Traditional AI relies on static training data, but ZOQ Agent accesses fresh, contextual information that makes prospects think "they actually researched me" instead of "this is clearly automated."&lt;/p&gt;

&lt;p&gt;Bright Data's reliable, real-time web access is what transforms generic AI into intelligent, contextual automation that drives real business results.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Give the &lt;a href="https://github.com/luminati-io/brightdata-mcp" rel="noopener noreferrer"&gt;Bright Data MCP repo&lt;/a&gt; some love! 🌟&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>brightdatachallenge</category>
      <category>ai</category>
      <category>webdata</category>
    </item>
    <item>
      <title>Check this out.</title>
      <dc:creator>Tamizh</dc:creator>
      <pubDate>Mon, 05 May 2025 15:18:51 +0000</pubDate>
      <link>https://dev.to/tamizhme/check-this-out-1b0g</link>
      <guid>https://dev.to/tamizhme/check-this-out-1b0g</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/tamizhme/scrapebase-permitio-web-scraping-with-authorization-1a9d" class="crayons-story__hidden-navigation-link"&gt;Scrapebase + Permit.io: Web Scraping with Authorization&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/tamizhme" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F1320145%2Ffac93a43-e4bc-488e-a927-5a6c200c8816.jpeg" alt="tamizhme profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/tamizhme" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Tamizh
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Tamizh
                
              
              &lt;div id="story-author-preview-content-2459758" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/tamizhme" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F1320145%2Ffac93a43-e4bc-488e-a927-5a6c200c8816.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Tamizh&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/tamizhme/scrapebase-permitio-web-scraping-with-authorization-1a9d" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 5 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/tamizhme/scrapebase-permitio-web-scraping-with-authorization-1a9d" id="article-link-2459758"&gt;
          Scrapebase + Permit.io: Web Scraping with Authorization
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/permitchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;permitchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/security"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;security&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/tamizhme/scrapebase-permitio-web-scraping-with-authorization-1a9d" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;152&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/tamizhme/scrapebase-permitio-web-scraping-with-authorization-1a9d#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              26&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>permitchallenge</category>
      <category>devchallenge</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Tamizh</dc:creator>
      <pubDate>Mon, 05 May 2025 14:23:01 +0000</pubDate>
      <link>https://dev.to/tamizhme/-432m</link>
      <guid>https://dev.to/tamizhme/-432m</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/tamizhme/scrapebase-permitio-web-scraping-with-authorization-1a9d" class="crayons-story__hidden-navigation-link"&gt;Scrapebase + Permit.io: Web Scraping with Authorization&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/tamizhme" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F1320145%2Ffac93a43-e4bc-488e-a927-5a6c200c8816.jpeg" alt="tamizhme profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/tamizhme" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Tamizh
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Tamizh
                
              
              &lt;div id="story-author-preview-content-2459758" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/tamizhme" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F1320145%2Ffac93a43-e4bc-488e-a927-5a6c200c8816.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Tamizh&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/tamizhme/scrapebase-permitio-web-scraping-with-authorization-1a9d" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 5 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/tamizhme/scrapebase-permitio-web-scraping-with-authorization-1a9d" id="article-link-2459758"&gt;
          Scrapebase + Permit.io: Web Scraping with Authorization
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/permitchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;permitchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/security"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;security&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/tamizhme/scrapebase-permitio-web-scraping-with-authorization-1a9d" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;152&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/tamizhme/scrapebase-permitio-web-scraping-with-authorization-1a9d#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              26&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>permitchallenge</category>
      <category>devchallenge</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>SaaS-Ready Web Scraping with Tiered Permissions using Permit.io</title>
      <dc:creator>Tamizh</dc:creator>
      <pubDate>Mon, 05 May 2025 06:15:54 +0000</pubDate>
      <link>https://dev.to/tamizhme/saas-ready-web-scraping-with-tiered-permissions-using-permitio-143d</link>
      <guid>https://dev.to/tamizhme/saas-ready-web-scraping-with-tiered-permissions-using-permitio-143d</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;I built &lt;strong&gt;Scrapebase&lt;/strong&gt; - a web scraping service that redefines how we think about permissions in modern SaaS applications. Instead of treating authorization as an afterthought, Scrapebase demonstrates how to build with permissions as a first-class concern from day one.&lt;/p&gt;

&lt;p&gt;The project solves several common authorization challenges:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tiered Access Control&lt;/strong&gt;: Managing different permission levels for free vs paid users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource-Level Restrictions&lt;/strong&gt;: Controlling access to sensitive domains&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature-Based Permissions&lt;/strong&gt;: Gating advanced features behind proper authorization&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tiered Service Levels&lt;/strong&gt;: Free, Pro, and Admin tiers with different capabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Key Authentication&lt;/strong&gt;: Simple authentication using API keys&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role-Based Access Control&lt;/strong&gt;: Permissions managed through Permit.io&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain Blacklist System&lt;/strong&gt;: Resource-level restrictions for sensitive domains&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text Processing&lt;/strong&gt;: Basic and advanced text processing with role-based restrictions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Permission Structure
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Free User&lt;/th&gt;
&lt;th&gt;Pro User&lt;/th&gt;
&lt;th&gt;Admin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basic Scraping&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Advanced Scraping&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text Cleaning&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Summarization&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;View Blacklist&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manage Blacklist&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access Blacklisted Domains&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;Try it live at: &lt;a href="https://scrapebase-permit.up.railway.app/" rel="noopener noreferrer"&gt;https://scrapebase-permit.up.railway.app/&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%2Ffu3y6x1k1f5iktb7wqu2.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%2Ffu3y6x1k1f5iktb7wqu2.png" alt="Demo" width="800" height="479"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshot: Demo page showing tiered access control in action&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Test it yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free User: &lt;code&gt;newuser&lt;/code&gt; / &lt;code&gt;2025DEVChallenge&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Admin: &lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;2025DEVChallenge&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Project Repo
&lt;/h2&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/0xtamizh/scrapebase-permit-IO" rel="noopener noreferrer"&gt;github.com/0xtamizh/scrapebase-permit-IO&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repository includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete source code with TypeScript&lt;/li&gt;
&lt;li&gt;Detailed setup instructions&lt;/li&gt;
&lt;li&gt;API documentation&lt;/li&gt;
&lt;li&gt;Example environment configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  My Journey
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Challenge
&lt;/h3&gt;

&lt;p&gt;Traditional approaches to authorization often result in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Permission checks scattered throughout code&lt;/li&gt;
&lt;li&gt;Security vulnerabilities from inconsistent enforcement&lt;/li&gt;
&lt;li&gt;Technical debt from hard-coded rules&lt;/li&gt;
&lt;li&gt;Difficulty in updating permission logic&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;I used Permit.io to create an externalized authorization system that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Separates business logic from authorization code&lt;/li&gt;
&lt;li&gt;Enables policy changes without code deployment&lt;/li&gt;
&lt;li&gt;Provides consistent permission enforcement&lt;/li&gt;
&lt;li&gt;Allows non-developers to manage permissions&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Challenges Faced
&lt;/h3&gt;

&lt;p&gt;The main challenge was implementing attribute-based access control (ABAC):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Initially tried ABAC (didn't work with cloud PDP)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;website&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;is_blacklisted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isBlacklistedDomain&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Had to simplify to RBAC&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;permissionCheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;website&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Learnings
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Technical Benefits&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean separation of concerns&lt;/li&gt;
&lt;li&gt;Externalized policy management&lt;/li&gt;
&lt;li&gt;Consistent enforcement&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Business Benefits&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Non-technical policy management&lt;/li&gt;
&lt;li&gt;Flexible permission updates&lt;/li&gt;
&lt;li&gt;Better security compliance&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Developer Experience&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduced complexity&lt;/li&gt;
&lt;li&gt;Better maintainability&lt;/li&gt;
&lt;li&gt;Focus on core features&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;The core authorization flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// permitAuth middleware&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;permitAuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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="nx"&gt;next&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;apiKey&lt;/span&gt; &lt;span class="o"&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;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// Map API key to user role&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapApiKeyToUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Sync with Permit.io&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;syncUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tier&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Check permission&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&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;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;website&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;allowed&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Access denied&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`User &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; cannot perform &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;action&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;span class="nf"&gt;next&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;h3&gt;
  
  
  Dashboard Configuration
&lt;/h3&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%2Fklxszl5jn1t89oa8hcuz.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%2Fklxszl5jn1t89oa8hcuz.png" alt="Dashboard: Resource Configuration" width="800" height="405"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Configuring resource types and actions in Permit.io dashboard&lt;/em&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%2Fdfrghtwx8cwsn41j64kc.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%2Fdfrghtwx8cwsn41j64kc.png" alt="Dashboard: Role Setup" width="800" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Setting up role-based permissions for different user tiers&lt;/em&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%2Fcggroc71gzazsaqfrlpa.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%2Fcggroc71gzazsaqfrlpa.png" alt="Dashboard: User Management" width="800" height="235"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Managing users and their role assignments&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Future Improvements
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Set up local PDP for ABAC support&lt;/li&gt;
&lt;li&gt;Implement tenant isolation&lt;/li&gt;
&lt;li&gt;Add permission audit logging UI&lt;/li&gt;
&lt;li&gt;Create more granular roles&lt;/li&gt;
&lt;li&gt;Add user management interface&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Scrapebase demonstrates how modern applications can redefine permissions by treating authorization as a first-class concern, enabling better security, maintainability, and user experience.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>permitchallenge</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Scrapebase + Permit.io: Web Scraping with Authorization</title>
      <dc:creator>Tamizh</dc:creator>
      <pubDate>Mon, 05 May 2025 06:11:17 +0000</pubDate>
      <link>https://dev.to/tamizhme/scrapebase-permitio-web-scraping-with-authorization-1a9d</link>
      <guid>https://dev.to/tamizhme/scrapebase-permitio-web-scraping-with-authorization-1a9d</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;: API-First Authorization Reimagined&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built &lt;strong&gt;Scrapebase&lt;/strong&gt; - a web scraping service with tiered access controls that demonstrates API-first authorization using Permit.io. The project separates business logic from authorization concerns using Permit.io's policy-as-code approach.&lt;/p&gt;

&lt;p&gt;In many applications, authorization is implemented as an afterthought, resulting in security vulnerabilities and technical debt. Scrapebase demonstrates how to build with authorization as a first-class concern from day one.&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%2Ffu3y6x1k1f5iktb7wqu2.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%2Ffu3y6x1k1f5iktb7wqu2.png" alt="Demo" width="800" height="479"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshot: Demo page : &lt;a href="https://scrapebase-permit.up.railway.app/" rel="noopener noreferrer"&gt;Click&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tiered Service Levels&lt;/strong&gt;: Free, Pro, and Admin tiers with different capabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Key Authentication&lt;/strong&gt;: Simple authentication using API keys&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role-Based Access Control&lt;/strong&gt;: Permissions managed through Permit.io&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain Blacklist System&lt;/strong&gt;: Resource-level restrictions for sensitive domains&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text Processing&lt;/strong&gt;: Basic and advanced text processing with role-based restrictions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Role-Based Capabilities
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Free User&lt;/th&gt;
&lt;th&gt;Pro User&lt;/th&gt;
&lt;th&gt;Admin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basic Scraping&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Advanced Scraping&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text Cleaning&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Summarization&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;View Blacklist&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manage Blacklist&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access Blacklisted Domains&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Try it live at: &lt;a href="https://scrapebase-permit.up.railway.app/" rel="noopener noreferrer"&gt;https://scrapebase-permit.up.railway.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Test Credentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free User: &lt;code&gt;newuser&lt;/code&gt; / &lt;code&gt;2025DEVChallenge&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Admin: &lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;2025DEVChallenge&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Project Repo
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1: Clone the repository
&lt;/h3&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/0xtamizh/scrapebase-permit-IO" rel="noopener noreferrer"&gt;github.com/0xtamizh/scrapebase-permit-IO&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;https://github.com/0xtamizh/scrapebase-permit-IO.git
&lt;span class="nb"&gt;cd &lt;/span&gt;scrapebase-permit-IO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Set up Permit.io
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create a free account at &lt;a href="https://permit.io" rel="noopener noreferrer"&gt;Permit.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create a new project&lt;/li&gt;
&lt;li&gt;Set up:

&lt;ul&gt;
&lt;li&gt;Resource type: &lt;code&gt;website&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Actions: &lt;code&gt;scrape_basic&lt;/code&gt;, &lt;code&gt;scrape_advanced&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Roles: &lt;code&gt;free_user&lt;/code&gt;, &lt;code&gt;pro_user&lt;/code&gt;, &lt;code&gt;admin&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Configure role permissions as described above&lt;/li&gt;
&lt;li&gt;Generate an Environment API key from the dashboard&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 3: Configure environment variables
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file in the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Permit.io
PERMIT_API_KEY=permit_env_YOUR_ENVIRONMENT_KEY

# API Keys for different user tiers
FREE_API_KEY=2025DEVChallenge_free
PRO_API_KEY=2025DEVChallenge_pro
ADMIN_API_KEY=2025DEVChallenge_admin

# Optional: For AI summarization
DEEPINFRA_API_KEY=your_deepinfra_key

# Server configuration
PORT=8080
NODE_ENV=development

# Browser manager settings
MAX_CONCURRENT_REQUESTS=50
REQUEST_TIMEOUT=60000
QUEUE_TIMEOUT=120000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Install dependencies and run
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Make sure to comment this line in src/utils/browserManager&lt;/span&gt;
//executablePath: process.env.CHROMIUM_PATH &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'/usr/bin/chromium-browser'&lt;/span&gt;, comment this line so it will use default chromium browser on your device

&lt;span class="c"&gt;# Run in development mode&lt;/span&gt;
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server will start on &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Test the application
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Using the UI:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt; in your browser&lt;/li&gt;
&lt;li&gt;"Log in" using the provided credentials

&lt;ul&gt;
&lt;li&gt;User credentials: &lt;code&gt;newuser&lt;/code&gt; / &lt;code&gt;2025DEVChallenge&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Admin credentials: &lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;2025DEVChallenge&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Toggle between Basic (Free) and Pro plans&lt;/li&gt;
&lt;li&gt;Enter a domain to scrape (e.g., example.com)&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Using the API directly:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Test with free user&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/api/processLinks &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: 2025DEVChallenge_free"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"url": "https://example.com"}'&lt;/span&gt;

&lt;span class="c"&gt;# Test with admin user&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/api/processLinks &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: 2025DEVChallenge_admin"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"url": "https://example.com", "advanced": true}'&lt;/span&gt;

&lt;span class="c"&gt;# Get blacklist&lt;/span&gt;
curl http://localhost:8080/api/blacklist &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: 2025DEVChallenge_free"&lt;/span&gt;

&lt;span class="c"&gt;# Add domain to blacklist (admin only)&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/api/blacklist &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: 2025DEVChallenge_admin"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"domain": "example.com"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  API-First Authorization
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Core Authorization Flow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;User sends request with &lt;code&gt;x-api-key&lt;/code&gt; header&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;permitAuth&lt;/code&gt; middleware intercepts the request&lt;/li&gt;
&lt;li&gt;Middleware maps API key to user role&lt;/li&gt;
&lt;li&gt;User is synced to Permit.io&lt;/li&gt;
&lt;li&gt;Permission check runs against Permit.io cloud PDP&lt;/li&gt;
&lt;li&gt;Request is allowed or denied based on policy decision
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────┐    ┌───────────────┐    ┌────────────┐    ┌──────────────┐
│  Client  │───▶│ Scrapebase API│───▶│permitAuth  │───▶│  Permit.io   │
│          │◀───│               │◀───│ middleware │◀───│  Cloud PDP   │
└──────────┘    └───────────────┘    └────────────┘    └──────────────┘
     │                                                        ▲
     │                                                        │
     └────────────────────────────────────────────────────────┘
       Permission policies defined in Permit.io dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;permitAuth&lt;/code&gt; middleware handles both role assignment and permission enforcement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Role assignment based on API key&lt;/span&gt;
&lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;case&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;ADMIN_API_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;userKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025DEVChallenge_admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ...other keys&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// User sync and permission check&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;syncUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&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="nx"&gt;userKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@scrapebase.xyz`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tier&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;permissionCheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;website&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dashboard Configuration
&lt;/h3&gt;

&lt;p&gt;For permissions to work correctly, you must configure roles and their allowed actions in the Permit.io dashboard:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create resource type &lt;code&gt;website&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create actions &lt;code&gt;scrape_basic&lt;/code&gt; and &lt;code&gt;scrape_advanced&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create roles &lt;code&gt;free_user&lt;/code&gt;, &lt;code&gt;pro_user&lt;/code&gt;, and &lt;code&gt;admin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Assign permissions to roles:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;free_user&lt;/code&gt;: Can &lt;code&gt;scrape_basic&lt;/code&gt; on &lt;code&gt;website&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pro_user&lt;/code&gt;: Can &lt;code&gt;scrape_basic&lt;/code&gt; and &lt;code&gt;scrape_advanced&lt;/code&gt; on &lt;code&gt;website&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;admin&lt;/code&gt;: Can do everything on &lt;code&gt;website&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Configuring resource types and actions in Permit.io dashboard&lt;/em&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%2Fklxszl5jn1t89oa8hcuz.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%2Fklxszl5jn1t89oa8hcuz.png" alt="Dashboard:Resource" width="800" height="405"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Setting up role-based permissions for different user tiers&lt;/em&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%2Fdfrghtwx8cwsn41j64kc.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%2Fdfrghtwx8cwsn41j64kc.png" alt="Dashboard:Roles" width="800" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Managing users and their role assignments&lt;/em&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%2Fcggroc71gzazsaqfrlpa.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%2Fcggroc71gzazsaqfrlpa.png" alt="Dashboard:Users" width="800" height="235"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Troubleshooting -&amp;gt; Check repo README
&lt;/h3&gt;
&lt;h2&gt;
  
  
  Challenges Faced
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Cloud PDP Limitations
&lt;/h3&gt;

&lt;p&gt;Initially, I tried implementing Attribute-Based Access Control (ABAC) by passing resource attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This DIDN'T work with cloud PDP&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;website&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;is_blacklisted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isBlacklistedDomain&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;permissionCheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cloud PDP returned 501 errors because it only supports basic RBAC. I had to simplify to a pure RBAC approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This works with cloud PDP&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;permissionCheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resourceType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;Traditional approaches to authorization often result in permission checks scattered throughout application code, creating maintenance nightmares and security risks. I created Scrapebase to demonstrate how modern applications can embrace externalized authorization as a core architectural principle.&lt;/p&gt;

&lt;p&gt;Scrapebase isn't just another CRUD app – it tackles a real-world use case (web scraping) with meaningful access control requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tiered service levels&lt;/strong&gt; that mirror SaaS subscription models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Administrative functions&lt;/strong&gt; that require elevated permissions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource-based restrictions&lt;/strong&gt; through the domain blacklist system&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What I Learned
&lt;/h3&gt;

&lt;p&gt;Building Scrapebase with Permit.io taught me how to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Technical Benefits&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Separation of authorization from business logic&lt;/li&gt;
&lt;li&gt;External policy management without code changes&lt;/li&gt;
&lt;li&gt;Scalable from RBAC to ABAC&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Business Benefits&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Non-developers can manage permissions&lt;/li&gt;
&lt;li&gt;Centralized policy management&lt;/li&gt;
&lt;li&gt;Better security through consistent enforcement&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Developer Experience&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cleaner codebase&lt;/li&gt;
&lt;li&gt;Focus on core features&lt;/li&gt;
&lt;li&gt;Better maintainability&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why Permit.io Works for SaaS
&lt;/h3&gt;

&lt;p&gt;Permit.io is ideal for SaaS applications because it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Centralizes policy management outside your codebase&lt;/li&gt;
&lt;li&gt;Provides a dashboard for non-developers to configure permissions&lt;/li&gt;
&lt;li&gt;Scales from simple RBAC to complex ABAC as your needs grow&lt;/li&gt;
&lt;li&gt;Offers audit logs for compliance and debugging&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This externalized approach enables business stakeholders to manage authorization policies directly through the Permit.io dashboard, while developers focus on building features - the hallmark of a well-designed API-first authorization system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future Improvements
&lt;/h3&gt;

&lt;p&gt;With more time, I would:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up a local PDP to enable ABAC with resource attributes&lt;/li&gt;
&lt;li&gt;Implement tenant isolation for multi-tenant support&lt;/li&gt;
&lt;li&gt;Add UI components in the admin dashboard to view permission audit logs&lt;/li&gt;
&lt;li&gt;Create more granular roles and permissions beyond the three tiers&lt;/li&gt;
&lt;li&gt;Add a user management section to assign roles through the UI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By implementing these controls through Permit.io rather than hardcoding them, Scrapebase demonstrates how authorization can be managed through declarative policies instead of imperative code – fulfilling the promise of truly API-first authorization.&lt;/p&gt;

</description>
      <category>permitchallenge</category>
      <category>devchallenge</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Scrapebase + Permit.io: Web Scraping with API-First Authorization</title>
      <dc:creator>Tamizh</dc:creator>
      <pubDate>Mon, 05 May 2025 05:15:26 +0000</pubDate>
      <link>https://dev.to/tamizhme/scrapebase-permitio-web-scraping-with-api-first-authorization-1i03</link>
      <guid>https://dev.to/tamizhme/scrapebase-permitio-web-scraping-with-api-first-authorization-1i03</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;I built &lt;strong&gt;Scrapebase&lt;/strong&gt; - a web scraping service with tiered access controls that demonstrates API-first authorization using Permit.io. The project separates business logic from authorization concerns using Permit.io's policy-as-code approach.&lt;/p&gt;

&lt;p&gt;In many applications, authorization is implemented as an afterthought, resulting in security vulnerabilities and technical debt. Scrapebase demonstrates how to build with authorization as a first-class concern from day one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tiered Service Levels&lt;/strong&gt;: Free, Pro, and Admin tiers with different capabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Key Authentication&lt;/strong&gt;: Simple authentication using API keys&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role-Based Access Control&lt;/strong&gt;: Permissions managed through Permit.io&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain Blacklist System&lt;/strong&gt;: Resource-level restrictions for sensitive domains&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text Processing&lt;/strong&gt;: Basic and advanced text processing with role-based restrictions&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The core authentication and authorization flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User sends request with &lt;code&gt;x-api-key&lt;/code&gt; header&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;permitAuth&lt;/code&gt; middleware intercepts the request&lt;/li&gt;
&lt;li&gt;Middleware maps API key to user role (&lt;code&gt;free_user&lt;/code&gt;, &lt;code&gt;pro_user&lt;/code&gt;, or &lt;code&gt;admin&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;User is synced to Permit.io&lt;/li&gt;
&lt;li&gt;Permission check runs against Permit.io cloud PDP&lt;/li&gt;
&lt;li&gt;Request is allowed or denied based on policy decision
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────┐    ┌───────────────┐    ┌────────────┐    ┌──────────────┐
│  Client  │───▶│ Scrapebase API│───▶│permitAuth  │───▶│  Permit.io   │
│          │◀───│               │◀───│ middleware │◀───│  Cloud PDP   │
└──────────┘    └───────────────┘    └────────────┘    └──────────────┘
     │                                                        ▲
     │                                                        │
     └────────────────────────────────────────────────────────┘
       Permission policies defined in Permit.io dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&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%2Fexample.com%2Fdemo-screenshot.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%2Fexample.com%2Fdemo-screenshot.png" alt="Scrapebase Demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can test the API using the following endpoints:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Test with free user&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/api/processLinks &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: 2025DEVChallenge_free"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"url": "https://example.com"}'&lt;/span&gt;

&lt;span class="c"&gt;# Test with admin user&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/api/processLinks &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: 2025DEVChallenge_admin"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"url": "https://example.com", "advanced": true}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/0xtamizh" rel="noopener noreferrer"&gt;
        0xtamizh
      &lt;/a&gt; / &lt;a href="https://github.com/0xtamizh/scrapebase-permit-IO" rel="noopener noreferrer"&gt;
        scrapebase-permit-IO
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Scrapebase with Permit.io Authorization&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A powerful web scraping API with fine-grained authorization controls powered by Permit.io. This project demonstrates how to implement sophisticated authorization patterns in a real-world API service
Demo- &lt;a href="https://scrapebase-permit.up.railway.app/" rel="nofollow noopener noreferrer"&gt;https://scrapebase-permit.up.railway.app/&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tiered Access Control&lt;/strong&gt;: Different permissions for Free, Pro, and Admin users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource-Based Authorization&lt;/strong&gt;: Control access based on target domains&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate Limiting&lt;/strong&gt;: Tier-specific rate limits enforced through policies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Scraping Features&lt;/strong&gt;: Premium capabilities restricted to Pro users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time Policy Updates&lt;/strong&gt;: Changes to permissions take effect immediately&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit Logging&lt;/strong&gt;: Track all authorization decisions&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick Start&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Clone the repository:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/0xtamizh/scrapebase-permit-IO
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; scrapebase-permit-IO&lt;/pre&gt;

&lt;/div&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Install dependencies:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install&lt;/pre&gt;

&lt;/div&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Set up environment variables:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;cp .env.example .env&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Edit &lt;code&gt;.env&lt;/code&gt; with your Permit.io API key and other configurations:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;PERMIT_API_KEY=your_permit_api_key
ADMIN_API_KEY=2025DEVChallenge_admin
USER_API_KEY=2025DEVChallenge_user
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start="4"&gt;
&lt;li&gt;Start the development server:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run dev&lt;/pre&gt;

&lt;/div&gt;
&lt;ol start="5"&gt;
&lt;li&gt;Visit &lt;a href="http://localhost:3000" rel="nofollow noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; to access the testing UI&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Testing the Authorization Features&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Test Credentials&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Admin User:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Username: admin&lt;/li&gt;
&lt;li&gt;API Key…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/0xtamizh/scrapebase-permit-IO" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

&lt;h3&gt;
  
  
  The Problem with Traditional Authorization
&lt;/h3&gt;

&lt;p&gt;Traditional approaches to authorization often result in permission checks scattered throughout application code, creating maintenance nightmares and security risks. When I started this project, I wanted to demonstrate how modern applications can embrace externalized authorization as a core architectural principle.&lt;/p&gt;

&lt;p&gt;I chose to build a web scraping service because it presents meaningful access control requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tiered service levels&lt;/strong&gt; that mirror real-world SaaS subscription models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Administrative functions&lt;/strong&gt; that require elevated permissions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource-based restrictions&lt;/strong&gt; through a domain blacklist system&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Power of API-First Authorization
&lt;/h3&gt;

&lt;p&gt;The key insight that drove this project was the separation of concerns: business logic should be distinct from authorization decisions. By using Permit.io, I was able to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define all permission policies in one place&lt;/li&gt;
&lt;li&gt;Enforce consistent access control across all endpoints&lt;/li&gt;
&lt;li&gt;Update policies without changing application code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The implementation was straightforward - here's the core middleware that powers the authorization flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Map API key to user role&lt;/span&gt;
&lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;case&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;ADMIN_API_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;userKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025DEVChallenge_admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ...other keys&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Sync user to Permit.io&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;syncUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&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="nx"&gt;userKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@scrapebase.xyz`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tier&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;span class="c1"&gt;// Check permission&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&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;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;advanced&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scrape_advanced&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="s1"&gt;scrape_basic&lt;/span&gt;&lt;span class="dl"&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;permissionCheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;website&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;permissionCheck&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Access denied by Permit.io&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;h3&gt;
  
  
  Challenges Faced
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Cloud PDP Limitations
&lt;/h4&gt;

&lt;p&gt;Initially, I tried implementing Attribute-Based Access Control (ABAC) by passing resource attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This DIDN'T work with cloud PDP&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;website&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;is_blacklisted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isBlacklistedDomain&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;permissionCheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cloud PDP returned 501 errors because it only supports basic RBAC. I had to simplify to a pure RBAC approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This works with cloud PDP&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;permissionCheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resourceType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Role Assignment
&lt;/h4&gt;

&lt;p&gt;Another challenge was ensuring roles were properly synchronized and recognized. The solution was two-fold:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Properly sync users with their role information&lt;/li&gt;
&lt;li&gt;Manually configure role permissions in the Permit.io dashboard&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Setting up Permit.io involved these key steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creating a project in the Permit.io dashboard&lt;/li&gt;
&lt;li&gt;Defining resources (&lt;code&gt;website&lt;/code&gt;), actions (&lt;code&gt;scrape_basic&lt;/code&gt;, &lt;code&gt;scrape_advanced&lt;/code&gt;), and roles (&lt;code&gt;free_user&lt;/code&gt;, &lt;code&gt;pro_user&lt;/code&gt;, &lt;code&gt;admin&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Configuring the permission matrix in the dashboard&lt;/li&gt;
&lt;li&gt;Integrating the Permit.io SDK into my application&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the role-based capability matrix I implemented:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Free User&lt;/th&gt;
&lt;th&gt;Pro User&lt;/th&gt;
&lt;th&gt;Admin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basic Scraping&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Advanced Scraping&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text Cleaning&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Summarization&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;View Blacklist&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manage Blacklist&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access Blacklisted Domains&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Permission Enforcement
&lt;/h3&gt;

&lt;p&gt;Permissions are enforced in two places:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;permitAuth&lt;/code&gt; middleware for API endpoints:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;permissionCheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;website&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;permissionCheck&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Access denied&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;ol&gt;
&lt;li&gt;Directly in route handlers for specific features:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="c1"&gt;// src/routes/summarize.ts&lt;/span&gt;
   &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;summarize&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;userTier&lt;/span&gt; &lt;span class="o"&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;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userTier&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pro_user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;userTier&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&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="k"&gt;return&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;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
         &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Access denied&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="na"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Text summarization is only available for Pro and Admin users&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;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Building Scrapebase with Permit.io taught me how to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Separate authorization concerns from business logic&lt;/li&gt;
&lt;li&gt;Implement role-based access control with external policy management&lt;/li&gt;
&lt;li&gt;Design a flexible permission system that doesn't require code changes to update policies&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The advantages of this approach are clear:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Separation of concerns&lt;/strong&gt;: Business logic remains focused on core functionality while authorization is handled externally&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adaptable policies&lt;/strong&gt;: Permissions can be updated without code changes or redeployments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent enforcement&lt;/strong&gt;: Authorization decisions follow the same rules across all application endpoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved security&lt;/strong&gt;: Centralized policy management reduces the risk of inconsistent permission checks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer experience&lt;/strong&gt;: Cleaner codebase with reduced authorization-related complexity&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This externalized approach enables business stakeholders to manage authorization policies directly through the Permit.io dashboard, while developers focus on building features - the hallmark of a well-designed API-first authorization system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Improvements
&lt;/h2&gt;

&lt;p&gt;With more time, I would:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up a local PDP to enable ABAC with resource attributes&lt;/li&gt;
&lt;li&gt;Implement tenant isolation for multi-tenant support&lt;/li&gt;
&lt;li&gt;Add UI components in the admin dashboard to view permission audit logs&lt;/li&gt;
&lt;li&gt;Create more granular roles and permissions beyond the three tiers&lt;/li&gt;
&lt;li&gt;Add a user management section to assign roles through the UI&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Scrapebase demonstrates how modern SaaS apps can delegate complex authorization to a specialized service like Permit.io, allowing developers to focus on core features while maintaining robust access controls.&lt;/p&gt;

</description>
      <category>permitio</category>
      <category>authorization</category>
      <category>api</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
