<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Swapnil Bhavsar]]></title><description><![CDATA[I work as Technical Lead at Square1. I am a full-stack developer with primarily focus on Laravel, Vue and React. Here, I blog about web and mobile development topics.]]></description><link>https://swapnil.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 10 Apr 2026 08:21:14 GMT</lastBuildDate><atom:link href="https://swapnil.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Remote Laravel Development with Tailscale, Herd & Mac Studio - The Complete Guide]]></title><description><![CDATA[A few days ago, I came across a post from Scott Tolinski on X about his remote development setup. He'd put together a full video walkthrough showing how he works on his home machine from anywhere. It was slick, but it was built around a JavaScript/No...]]></description><link>https://swapnil.dev/remote-laravel-development-with-tailscale-herd-and-mac-studio-the-complete-guide</link><guid isPermaLink="true">https://swapnil.dev/remote-laravel-development-with-tailscale-herd-and-mac-studio-the-complete-guide</guid><category><![CDATA[Laravel]]></category><category><![CDATA[tailscale]]></category><category><![CDATA[Remote Development]]></category><dc:creator><![CDATA[Swapnil Bhavsar]]></dc:creator><pubDate>Sun, 08 Feb 2026 05:23:11 GMT</pubDate><content:encoded><![CDATA[<p>A few days ago, I came across a <a target="_blank" href="https://x.com/stolinski">post from Scott Tolinski</a> on X about his remote development setup. He'd put together a <a target="_blank" href="https://www.youtube.com/watch?v=G0sEM9ijkTE">full video walkthrough</a> showing how he works on his home machine from anywhere. It was slick, but it was built around a JavaScript/Node workflow. As a Laravel developer, I immediately thought: <em>I want this, but for my stack.</em></p>
<p>That video was the push I needed. Because here's the thing, every time I go on vacation, I tell myself I won't need to touch code. And every time, something comes up, a client finds a bug, a deployment needs a quick fix, or I just get an idea I want to prototype before I forget it.</p>
<p>My setup at home is a Mac Studio. It's my workhorse, all my Laravel projects, databases, Redis, everything runs on it through Laravel Herd. When I travel, I carry a MacBook Air. It's light, the battery lasts forever, and it's perfect for everything <em>except</em> serious development.</p>
<p>For years, my "remote workflow" was embarrassingly manual. Before every trip, I'd spend an hour backing up databases, pushing half-finished branches to Git, cloning repos on the MacBook Air, importing databases, configuring <code>.env</code> files, installing dependencies... only to realize I forgot to export that one Minio bucket, or that the MacBook Air is running a different PHP version, or that some queue worker behaves differently because the local data is stale.</p>
<p>And then when I got back home, I'd have to merge everything back, re-import any database changes, and hope nothing got out of sync. It was painful. I knew there had to be a better way.</p>
<p>Turns out, there is. What if my MacBook Air could just... <em>use</em> the Mac Studio directly? Same projects, same databases, same everything, as if I were sitting at my desk at home, but from a hotel room in another city?</p>
<p>That's exactly what I built. Here's the full guide.</p>
<hr />
<h2 id="heading-the-goal">The Goal</h2>
<p>I wanted to be able to:</p>
<ul>
<li><p>Access my Laravel <code>.test</code> domains (like <code>example.test</code>, <code>laravel.test</code>) over both HTTP and HTTPS from my MacBook Air, anywhere in the world</p>
</li>
<li><p>Edit code remotely using Zed editor's SSH feature</p>
</li>
<li><p>Run terminal commands on my Mac Studio via SSH</p>
</li>
<li><p>Access my home LAN devices (like Home Assistant) through the Mac Studio</p>
</li>
</ul>
<p>All without exposing anything to the public internet. No port forwarding, no dynamic DNS, no VPS middleman.</p>
<h2 id="heading-what-youll-need">What You'll Need</h2>
<ul>
<li><p><strong>Mac Studio</strong> (or any always-on Mac) at home running <strong>Laravel Herd</strong></p>
</li>
<li><p><strong>MacBook Air</strong> (or any portable Mac) as your travel machine</p>
</li>
<li><p><strong>Tailscale</strong> installed on both machines (free tier works)</p>
</li>
</ul>
<hr />
<h2 id="heading-step-1-set-up-tailscale-on-both-machines">Step 1: Set Up Tailscale on Both Machines</h2>
<p><a target="_blank" href="https://tailscale.com">Tailscale</a> creates a private WireGuard-based mesh network between your devices. It works through NATs and firewalls without any port forwarding, which is exactly what makes this whole setup possible.</p>
<p>Install Tailscale on both your Mac Studio and MacBook Air. Once connected, each machine gets a stable IP on the <code>100.x.x.x</code> range.</p>
<p>In my case:</p>
<ul>
<li><p>Mac Studio: <code>100.64.1.10</code></p>
</li>
<li><p>MacBook Air: <code>100.64.1.20</code></p>
</li>
</ul>
<h3 id="heading-configure-mac-studio-as-an-exit-node">Configure Mac Studio as an Exit Node</h3>
<p>On the Mac Studio, enable it as an exit node with local network access. This is done through the Tailscale menu bar app or via CLI:</p>
<pre><code class="lang-bash">tailscale up --advertise-exit-node --advertise-routes=192.168.1.0/24
</code></pre>
<p>Replace <code>192.168.1.0/24</code> with your home LAN subnet. This enables subnet routing so your MacBook Air can reach local LAN devices (like a NAS, Home Assistant, or a printer) through the Mac Studio.</p>
<p>Don't forget to approve the exit node and subnet routes in the <a target="_blank" href="https://login.tailscale.com/admin/machines">Tailscale admin console</a>.</p>
<hr />
<h2 id="heading-step-2-configure-laravel-herd-to-accept-remote-connections">Step 2: Configure Laravel Herd to Accept Remote Connections</h2>
<p>By default, Herd's Nginx only listens on <code>127.0.0.1</code>, which means it rejects connections from any IP other than localhost, including your Tailscale IP. We need to change this.</p>
<h3 id="heading-update-nginx-to-listen-on-all-interfaces">Update Nginx to Listen on All Interfaces</h3>
<p>Herd stores its Nginx site configs here:</p>
<pre><code class="lang-plaintext">~/Library/Application Support/Herd/config/valet/Nginx/
</code></pre>
<p>Each <code>.test</code> domain has its own config file. You need to change the <code>listen</code> directives from <code>127.0.0.1</code> to <code>0.0.0.0</code> for both port 80 (HTTP) and port 443 (HTTPS).</p>
<p>You can do this in bulk with a single command on the Mac Studio:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Update HTTP (port 80)</span>
sed -i <span class="hljs-string">''</span> <span class="hljs-string">'s/listen 127.0.0.1:80/listen 0.0.0.0:80/g'</span> ~/Library/Application\ Support/Herd/config/valet/Nginx/*

<span class="hljs-comment"># Update HTTPS (port 443)</span>
sed -i <span class="hljs-string">''</span> <span class="hljs-string">'s/listen 127.0.0.1:443 ssl/listen 0.0.0.0:443 ssl/g'</span> ~/Library/Application\ Support/Herd/config/valet/Nginx/*

<span class="hljs-comment"># Restart Nginx</span>
herd restart nginx
</code></pre>
<blockquote>
<p><strong>Heads up:</strong> Herd may regenerate these configs when you add or remove sites, or when Herd itself updates. I keep this as a shell script so I can re-run it in seconds (more on that later).</p>
</blockquote>
<hr />
<h2 id="heading-step-3-configure-dns-on-macbook-air">Step 3: Configure DNS on MacBook Air</h2>
<p>Your MacBook Air needs to know that <code>.test</code> domains should resolve to the Mac Studio's Tailscale IP. The browser doesn't know that <code>example.test</code> lives on <code>100.64.1.10</code> — we need to tell it. We'll use <code>dnsmasq</code> for this.</p>
<h3 id="heading-install-and-configure-dnsmasq">Install and Configure dnsmasq</h3>
<pre><code class="lang-bash">brew install dnsmasq
</code></pre>
<p>Edit the dnsmasq config:</p>
<pre><code class="lang-bash">nano /opt/homebrew/etc/dnsmasq.conf
</code></pre>
<p>Add this line (replace with your Mac Studio's Tailscale IP):</p>
<pre><code class="lang-plaintext">address=/.test/100.64.1.10
</code></pre>
<blockquote>
<p><strong>Note the dot before</strong> <code>test</code> , <code>address=/.test/...</code> matches all subdomains under <code>.test</code>. Without the leading dot, it won't catch everything correctly.</p>
</blockquote>
<p>Start dnsmasq:</p>
<pre><code class="lang-bash">sudo brew services start dnsmasq
</code></pre>
<h3 id="heading-point-macos-to-use-dnsmasq-for-test-domains">Point macOS to Use dnsmasq for .test Domains</h3>
<p>Create a resolver file:</p>
<pre><code class="lang-bash">sudo mkdir -p /etc/resolver
sudo bash -c <span class="hljs-string">'echo "nameserver 127.0.0.1" &gt; /etc/resolver/test'</span>
</code></pre>
<p>Verify it works:</p>
<pre><code class="lang-bash">ping example.test
<span class="hljs-comment"># Should resolve to 100.64.1.10</span>
</code></pre>
<hr />
<h2 id="heading-step-4-enable-ssh-on-mac-studio">Step 4: Enable SSH on Mac Studio</h2>
<p>Go to <strong>System Settings → General → Sharing → Remote Login</strong> and turn it on. This lets you SSH into the Mac Studio using its Tailscale IP.</p>
<p>Test from MacBook Air:</p>
<pre><code class="lang-bash">ssh yourusername@100.64.1.10
</code></pre>
<p>I'd also recommend setting up SSH key authentication so you don't have to type your password every time. It also makes Zed's remote editing much smoother.</p>
<hr />
<h2 id="heading-step-5-set-up-zed-editor-for-remote-development">Step 5: Set Up Zed Editor for Remote Development</h2>
<p>This is where the magic really happens. <a target="_blank" href="https://zed.dev">Zed</a> has built-in SSH remote editing support, it feels almost indistinguishable from editing local files.</p>
<p>On your MacBook Air:</p>
<ol>
<li><p>Open the command palette (<code>Cmd+Shift+P</code>)</p>
</li>
<li><p>Search for <strong>"Connect via SSH"</strong></p>
</li>
<li><p>Enter your Mac Studio's Tailscale IP and the path to your project</p>
</li>
</ol>
<p>Zed opens the remote folder with full LSP support (so you get autocompletion, go-to-definition, error highlighting, all powered by the Mac Studio's PHP/Node/etc.). You also get an integrated terminal that runs commands directly on the Mac Studio.</p>
<p>I was honestly surprised at how good this feels. There's a tiny bit of latency on autocomplete, but it's barely noticeable over Tailscale's WireGuard connection. It's miles better than working off a stale local clone.</p>
<hr />
<h2 id="heading-step-6-fix-https-the-tricky-part">Step 6: Fix HTTPS — The Tricky Part</h2>
<p>At this point, <code>http://example.test</code> works beautifully from the MacBook Air. But <code>https://example.test</code>? Nope. The connection just hangs or throws an SSL error.</p>
<p>This took me a while to figure out. There are actually two separate problems, and you need to fix both.</p>
<h3 id="heading-problem-1-tailscale-serve-hijacking-port-443">Problem 1: Tailscale Serve Hijacking Port 443</h3>
<p>If you've ever used <code>tailscale serve</code> on your Mac Studio (even once, even experimentally), it may still be listening on port 443. This means Tailscale intercepts all HTTPS traffic before Nginx ever sees it.</p>
<p>I spent way too long staring at Nginx configs before I thought to check this. Here's how to diagnose:</p>
<pre><code class="lang-bash">sudo lsof -iTCP:443 -sTCP:LISTEN -nP
</code></pre>
<p>If you see <code>io.tailsc</code> instead of <code>nginx</code>, that's your problem. Tailscale is eating your HTTPS traffic.</p>
<p>Fix it:</p>
<pre><code class="lang-bash">tailscale serve off
</code></pre>
<p><strong>Don't worry</strong>, this is completely safe. It only disables the HTTPS proxy feature. Your Tailscale VPN tunnel, exit node, subnet routes, and SSH all continue working perfectly since they use WireGuard over UDP, not TCP port 443.</p>
<p>Now restart Nginx so it can claim the port:</p>
<pre><code class="lang-bash">herd restart nginx
</code></pre>
<p>Verify Nginx is now on 443:</p>
<pre><code class="lang-bash">sudo lsof -iTCP:443 -sTCP:LISTEN -nP
<span class="hljs-comment"># Should show nginx-arm, not io.tailsc</span>
</code></pre>
<h3 id="heading-problem-2-untrusted-ssl-certificate-on-macbook-air">Problem 2: Untrusted SSL Certificate on MacBook Air</h3>
<p>Herd uses its own Certificate Authority (CA) to generate SSL certificates for <code>.test</code> domains. This CA is automatically trusted on the Mac Studio (where Herd installed it into the Keychain), but your MacBook Air has no idea it exists.</p>
<p>So when you access <code>https://example.test</code> from the MacBook Air, the browser sees a certificate signed by an unknown CA and rightfully rejects it.</p>
<p>The fix is simple: copy the CA certificate to the MacBook Air and trust it.</p>
<p><strong>Copy the CA certificate:</strong></p>
<pre><code class="lang-bash"><span class="hljs-comment"># Run this on your MacBook Air</span>
scp yourusername@100.64.1.10:<span class="hljs-string">"~/Library/Application Support/Herd/config/valet/CA/LaravelValetCASelfSigned.pem"</span> ~/Desktop/
</code></pre>
<p><strong>Trust it in Keychain Access:</strong></p>
<ol>
<li><p>Double-click <code>LaravelValetCASelfSigned.pem</code> - it opens Keychain Access</p>
</li>
<li><p>When prompted, select the <strong>System</strong> keychain (important - not login, not iCloud)</p>
</li>
<li><p>Find <strong>"Laravel Valet CA Self Signed CN"</strong> in the certificate list</p>
</li>
<li><p>Double-click it → expand <strong>Trust</strong> → set <strong>"When using this certificate"</strong> to <strong>Always Trust</strong></p>
</li>
<li><p>Close and enter your password to confirm</p>
</li>
</ol>
<p>Now test:</p>
<pre><code class="lang-bash">curl https://example.test
<span class="hljs-comment"># Should return your site's HTML without any SSL errors</span>
</code></pre>
<p>Open <code>https://example.test</code> in your browser, you should see the padlock with a valid certificate. That moment felt incredibly satisfying after all the debugging.</p>
<hr />
<h2 id="heading-the-final-result">The Final Result</h2>
<p>With everything configured, here's what my workflow looks like now:</p>
<p>I open my MacBook Air in a hotel, a café, or my in-laws' living room. Tailscale connects automatically within seconds. I open <code>https://example.test</code> in the browser and my Laravel app loads, with valid HTTPS, hitting the real database, with all my test data intact. I open Zed, connect via SSH, and I'm editing the actual project files on the Mac Studio. The integrated terminal runs artisan commands, queue workers, migrations, everything, directly on the Mac Studio.</p>
<p>No more cloning repos. No more exporting databases. No more "works on my machine" discrepancies between two laptops. It's the same machine, the same environment, the same data. I just happen to be 500 kilometers away from it.</p>
<hr />
<h2 id="heading-quick-reference-commands-to-re-run-after-herd-updates">Quick Reference: Commands to Re-Run After Herd Updates</h2>
<p>Herd sometimes resets Nginx configs when you add/remove sites or update the app. Here's a handy script to fix everything in one go:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
<span class="hljs-comment"># fix-herd-remote.sh — Run on Mac Studio after Herd updates</span>

<span class="hljs-comment"># Allow remote connections on HTTP and HTTPS</span>
sed -i <span class="hljs-string">''</span> <span class="hljs-string">'s/listen 127.0.0.1:80/listen 0.0.0.0:80/g'</span> ~/Library/Application\ Support/Herd/config/valet/Nginx/*
sed -i <span class="hljs-string">''</span> <span class="hljs-string">'s/listen 127.0.0.1:443 ssl/listen 0.0.0.0:443 ssl/g'</span> ~/Library/Application\ Support/Herd/config/valet/Nginx/*

<span class="hljs-comment"># Restart Nginx</span>
herd restart nginx

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Done! Remote access restored."</span>
</code></pre>
<p>Save it somewhere handy and <code>chmod +x fix-herd-remote.sh</code>. I keep mine in <code>~/scripts/</code> and run it after every Herd update.</p>
<hr />
<h2 id="heading-troubleshooting">Troubleshooting</h2>
<p><strong>Can't connect to port 80 or 443?</strong> Check what's listening: <code>sudo lsof -iTCP:80 -sTCP:LISTEN -nP</code> and <code>sudo lsof -iTCP:443 -sTCP:LISTEN -nP</code>. You want to see <code>nginx</code>, not <code>io.tailsc</code> or nothing at all.</p>
<p><strong>SSL handshake fails with "internal error" and no certificate?</strong> Tailscale Serve is almost certainly intercepting port 443. Run <code>tailscale serve status</code> to confirm, then <code>tailscale serve off</code> to fix it.</p>
<p><strong>Certificate shows as untrusted on MacBook Air?</strong> Make sure you imported the Herd CA into the <strong>System</strong> keychain (not login or iCloud) and set it to <strong>Always Trust</strong>. Some browsers need a restart to pick up new CA certificates.</p>
<p><strong>DNS not resolving .test domains on MacBook Air?</strong> Check that dnsmasq is running (<code>brew services list</code>), the resolver file exists (<code>cat /etc/resolver/test</code>), and the dnsmasq config has the correct syntax: <code>address=/.test/100.64.1.10</code> (note the dot before <code>test</code>).</p>
<p><strong>Herd reset my Nginx configs?</strong> Re-run the <code>fix-herd-remote.sh</code> script above. Takes two seconds.</p>
<hr />
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>This setup has completely changed how I think about travel and work. I no longer dread being away from my desk. The MacBook Air has gone from "I guess I can do basic stuff on it" to a full portal into my development environment. The M-series chips on the Mac Studio handle all the heavy lifting, and I get to enjoy the portability of the Air without any compromises.</p>
<p>If you're a Laravel developer with a desktop Mac at home and a laptop for travel, I can't recommend this enough. The initial setup takes about 30 minutes, and it saves hours of painful sync work on every single trip.</p>
<p>Happy coding from wherever you are! ❤️</p>
]]></content:encoded></item><item><title><![CDATA[Upgrading My Home Network with Mesh WiFi]]></title><description><![CDATA[This isn't a typical how-to guide—it's the story of my experience upgrading my home network. I'm not an expert, but I learned a lot along the way, and I hope sharing my journey might help others facing similar challenges.
Recently, I decided to tackl...]]></description><link>https://swapnil.dev/upgrading-my-home-network-with-mesh-wifi</link><guid isPermaLink="true">https://swapnil.dev/upgrading-my-home-network-with-mesh-wifi</guid><category><![CDATA[Homelab]]></category><category><![CDATA[tp link]]></category><category><![CDATA[Home Server]]></category><dc:creator><![CDATA[Swapnil Bhavsar]]></dc:creator><pubDate>Wed, 16 Oct 2024 06:21:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/PSpf_XgOM5w/upload/9e48089f51f0b460d64826796ab8ed8e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>This isn't a typical how-to guide—it's the story of my experience upgrading my home network. I'm not an expert, but I learned a lot along the way, and I hope sharing my journey might help others facing similar challenges.</em></p>
<p>Recently, I decided to tackle the persistent Wi-Fi range issues in my home. I had been using a <strong>TP-Link Archer AX73</strong> router, which delivered great speeds but couldn't provide sufficient coverage throughout the house. Dead zones and weak signals were becoming daily frustrations, hindering everything from streaming movies to working remotely.</p>
<p>To address these problems, I invested in a <strong>TP-Link Deco X60 mesh system</strong> with three units. I was excited about the promise of seamless Wi-Fi coverage and better connectivity. However, integrating the new hardware wasn't as straightforward as I anticipated.</p>
<p>This is the story of how I set up the Deco X60 mesh network, the challenges I encountered—like losing access to my home server—and how I overcame them by learning about IP addresses, DHCP ranges, and the crucial role of subnet masks in network configuration.</p>
<h3 id="heading-the-initial-setup">The Initial Setup</h3>
<p>My home network consisted of:</p>
<ul>
<li><p><strong>Router:</strong> TP-Link Archer AX73</p>
</li>
<li><p><strong>Home Server:</strong> Set to a static IP address <code>192.168.1.100</code></p>
</li>
<li><p><strong>Issue:</strong> Wi-Fi coverage was spotty in certain areas of the house</p>
</li>
</ul>
<p>I relied on my home server for various tasks, accessible via SSH at <code>192.168.1.100</code>. However, the limited Wi-Fi range of the AX73 meant that devices in other parts of the house couldn't maintain a stable connection.</p>
<h3 id="heading-introducing-the-deco-x60-mesh-system">Introducing the Deco X60 Mesh System</h3>
<p>To resolve the Wi-Fi coverage problem, I replaced the AX73 with a TP-Link Deco X60 mesh system, installing three units around my home. The expectation was that the mesh network would provide seamless Wi-Fi coverage everywhere.</p>
<h4 id="heading-setting-up-the-deco-x60">Setting Up the Deco X60</h4>
<ul>
<li><p><strong>Connected the Main Deco Unit:</strong> Hooked it up to my modem.</p>
</li>
<li><p><strong>Placed Additional Units:</strong> Positioned the other two Deco units strategically around the house.</p>
</li>
<li><p><strong>Used the Deco App:</strong> Followed the in-app instructions to set up the network.</p>
</li>
</ul>
<p>After the initial setup, Wi-Fi coverage was indeed much better. However, a new problem arose: I couldn't access my home server anymore.</p>
<h3 id="heading-the-problem-unable-to-access-home-server">The Problem: Unable to Access Home Server</h3>
<p>Even though devices were connected to the new mesh network, I couldn't SSH into my home server at <code>192.168.1.100</code>. I was not able ping the server or access the <a target="_blank" href="https://www.proxmox.com/en/">Proxmox</a> panel.</p>
<h3 id="heading-troubleshooting-steps">Troubleshooting Steps</h3>
<h4 id="heading-1-updating-the-deco-x60s-ip-address">1. Updating the Deco X60's IP Address</h4>
<p>I suspected that the issue was related to IP addressing, as the Deco X60 was accessible at <code>192.168.68.1</code>. I decided to update the IP address of the main deco unit.</p>
<ul>
<li><p><strong>Accessed the Deco App:</strong></p>
<ul>
<li>Opened the app on my smartphone.</li>
</ul>
</li>
<li><p><strong>Navigated to Network Settings:</strong></p>
<ul>
<li>Went to the <strong>'More'</strong> tab and selected <strong>'Advanced'</strong>.</li>
</ul>
</li>
<li><p><strong>Changed the LAN IP Address:</strong></p>
<ul>
<li><p>Selected <strong>'LAN IP'</strong>.</p>
</li>
<li><p>Updated the LAN IP to <code>192.168.1.1</code>, matching the IP of my old AX73 router.</p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-2-adjusting-the-dhcp-range">2. Adjusting the DHCP Range</h4>
<p>Since we changed the IP address, I decided to change DHCP range to as well.</p>
<ul>
<li><p><strong>Accessed DHCP Settings:</strong></p>
<ul>
<li>Still in the <strong>'Advanced'</strong> section, I selected <strong>'DHCP Server'</strong>.</li>
</ul>
</li>
<li><p><strong>Updated the DHCP IP Range:</strong></p>
<ul>
<li>Changed the IP range from <code>192.168.1.50</code> to <code>192.168.3.250</code>.</li>
</ul>
</li>
<li><p><strong>Saved the Settings:</strong></p>
<ul>
<li>Applied the changes to ensure all devices could obtain IP addresses within this range.</li>
</ul>
</li>
</ul>
<h3 id="heading-result-after-changes">Result After Changes</h3>
<p>After updating the LAN IP and DHCP range, I was able to ping the home server at <code>192.168.1.100</code>. Hurray!!! 🎉</p>
<p>However, when I tried SSH in to server, I received following error when attempting to connect.</p>
<pre><code class="lang-bash">kex_exchange_identification: <span class="hljs-built_in">read</span>: Connection reset by peer
Connection reset by 192.168.1.100 port 22
</code></pre>
<h3 id="heading-the-solution-fixing-the-subnet-mask">The Solution: Fixing the Subnet Mask</h3>
<p>It dawned on me that the subnet mask might be causing devices to think they were on different networks, even though their IP addresses were correct this time.</p>
<p>I saw subnet mask was configured to <code>255.255.0.0</code> in Deco X60. So I decided to change it &amp; give it a try.</p>
<h4 id="heading-updating-the-subnet-mask-on-the-deco-x60">Updating the Subnet Mask on the Deco X60</h4>
<ul>
<li><p><strong>Accessed LAN IP Settings:</strong></p>
<ul>
<li>In the Deco app, under <strong>'Advanced'</strong> &gt; <strong>'LAN IP'</strong>.</li>
</ul>
</li>
<li><p><strong>Set the Subnet Mask:</strong></p>
<ul>
<li>Changed it to <code>255.255.255.0</code>.</li>
</ul>
</li>
</ul>
<h4 id="heading-verifying-the-servers-network-configuration">Verifying the Server's Network Configuration</h4>
<ul>
<li><p><strong>Checked the Server's Subnet Mask:</strong></p>
<ul>
<li>Ensured it was also set to <code>255.255.255.0</code>.</li>
</ul>
</li>
<li><p><strong>Confirmed the Default Gateway:</strong></p>
<ul>
<li>Made sure the server's gateway was <code>192.168.1.1</code>.</li>
</ul>
</li>
</ul>
<h4 id="heading-restarting-devices">Restarting Devices</h4>
<p>Restarted my home server, computers, and any other network devices to apply the new settings.</p>
<h2 id="heading-success-at-last">Success at Last</h2>
<p>After fixing the subnet mask, everything started working as expected. I could SSH into my home server at <code>192.168.1.100</code> without any issues. The network was now correctly configured, and all devices could communicate seamlessly.</p>
<h2 id="heading-what-i-learned">What I Learned</h2>
<p>This entire process was a significant learning experience. I'm not a networking expert, but I gained valuable insights:</p>
<ul>
<li><p><strong>The Importance of Subnet Masks:</strong></p>
<ul>
<li>The subnet mask must be consistent across devices to ensure they're on the same network.</li>
</ul>
</li>
<li><p><strong>Consistent IP Configuration:</strong></p>
<ul>
<li>Updating the router's IP to match the old configuration prevented conflicts.</li>
</ul>
</li>
<li><p><strong>Adjusting DHCP Range:</strong></p>
<ul>
<li>Ensuring the DHCP range accommodates all devices is crucial, especially with multiple units.</li>
</ul>
</li>
<li><p><strong>Patience in Troubleshooting:</strong></p>
<ul>
<li>Networking issues can be complex; methodically checking each setting helps identify the problem.</li>
</ul>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Switching to a mesh network with the Deco X60 significantly improved my home's Wi-Fi coverage. However, the journey wasn't without its challenges. By updating the LAN IP, adjusting the DHCP range, and fixing the subnet mask, I was able to resolve the connectivity issues with my home server.</p>
<p>This experience taught me the critical role that subnet masks and proper network configurations play in a home network. While I'm not an expert, I now have a better understanding of how these settings impact device communication.</p>
<p><strong>Tips for Others:</strong></p>
<ul>
<li><p><strong>Check Default Network Settings:</strong> New routers may have default IPs and subnet masks that differ from your existing setup.</p>
</li>
<li><p><strong>Ensure Consistent Subnet Masks:</strong> This ensures all devices are recognized on the same network.</p>
</li>
<li><p><strong>Adjust DHCP Ranges as Needed:</strong> Especially important when adding multiple units to your network.</p>
</li>
<li><p><strong>Don't Hesitate to Learn:</strong> Troubleshooting can be a great learning opportunity.</p>
</li>
</ul>
<p>Upgrading your home network can bring significant benefits, and with a bit of patience and willingness to learn, you can overcome any hurdles along the way.</p>
]]></content:encoded></item><item><title><![CDATA[Authentication in Nuxt.js using Laravel Sanctum]]></title><description><![CDATA[You should never save authorization tokens in local storage or cookies, as they can be accessed by any third-party JavaScript code running in the user's browser. By simply using a code like this localStorage.getItem('auth_token'), tokens can be stole...]]></description><link>https://swapnil.dev/authentication-in-nuxtjs-using-laravel-sanctum</link><guid isPermaLink="true">https://swapnil.dev/authentication-in-nuxtjs-using-laravel-sanctum</guid><category><![CDATA[Laravel]]></category><category><![CDATA[Nuxt.js]]></category><category><![CDATA[Nuxt]]></category><dc:creator><![CDATA[Swapnil Bhavsar]]></dc:creator><pubDate>Fri, 22 Apr 2022 10:37:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1681395523991/d052337c-df26-4a75-899a-b834eafbce9a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You should never save authorization tokens in local storage or cookies, as they can be accessed by any third-party JavaScript code running in the user's browser. By simply using a code like this <code>localStorage.getItem('auth_token')</code>, tokens can be stolen and misused. By saving tokens in local storage or insecure cookies, you are jeopardizing the security of your user's account.</p>
<p><a target="_blank" href="https://github.com/laravel/sanctum">Laravel Sanctum</a> is a package made by <a target="_blank" href="https://twitter.com/taylorotwell">Taylor Otwell</a> which solves this issue by using a special kind of cookies called <code>HttpOnly</code> cookies. These cookies are set by the server, and cannot be read by the JavaScript code running on the client-side <em>aka</em> browser. When these cookies are set by the server, the browser automatically sends these cookies back to the server with every request, and thus the server knows it an authorized request.</p>
<blockquote>
<p>Laravel Sanctum also takes care of CSRF protection by including CSRF cookie in each request. And also protects your site against XSS based attacks.</p>
</blockquote>
<h2 id="heading-introduction">Introduction</h2>
<p>In this tutorial, we will build a sample <a target="_blank" href="https://nuxtjs.org">Nuxt.js</a> application which will demo the authentication flow using Laravel sanctum. We are going to build our Nuxt application in the SPA (<a target="_blank" href="https://en.wikipedia.org/wiki/Single-page_application">Single page application</a>) mode. If you want to deploy your application in universal (SSR - Server-side rendered) mode, you can still do it.</p>
<p>Authentication in the Nuxt using Laravel sanctum does work in SSR mode. But it doesn't make much sense if your application running SSR mode if the application requires login to access and search engine can access your site without a login.</p>
<h4 id="heading-spa-and-backend-domains">SPA and Backend domains</h4>
<p>To work with Sanctum, we should be familiar with a few things first. We must use our SPA and API backend on the same domain, like frontend on <a target="_blank" href="http://domain.com"><code>domain.com</code></a> and API on <a target="_blank" href="http://api.domain.com"><code>api.domain.com</code></a>. We cannot set frontend on <a target="_blank" href="http://domain.com">domain.com</a> and backend (API) on <a target="_blank" href="http://another-domain.com"><code>another-domain.com</code></a>. The client must be able to include cookies with each request being sent to the backend.</p>
<p>Please check out Mozilla developer documentation on <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials">withCredentials</a> &amp; article by <a target="_blank" href="https://twitter.com/themsaid">Mohamed Said</a> on <a target="_blank" href="https://divinglaravel.com/authentication-and-laravel-airlock">Authentication and Laravel Sanctum</a> for information on session using cookies.</p>
<blockquote>
<p>Here is my little advice, when you are developing the Nuxt site in local, use <code>php artisan serve</code> command to serve Laravel backend at <a target="_blank" href="http://localhost:8000"><code>http://localhost:8000</code></a> instead of using valet like <code>sanctum-nuxt.test</code>.</p>
</blockquote>
<h2 id="heading-setting-up-laravel-sanctum">Setting up Laravel Sanctum</h2>
<p>Enough of theory! Let's begin by setting up Laravel sanctum for your Laravel application. Just follow these steps carefully to configure your app.</p>
<p>In your Laravel 7 app, install the sanctum package using composer:</p>
<pre><code class="lang-bash">composer require laravel/sanctum
</code></pre>
<p>Next, publish sanctum configuration &amp; database migration files.</p>
<pre><code class="lang-bash">php artisan vendor:publish --provider=<span class="hljs-string">"Laravel\Sanctum\SanctumServiceProvider"</span>
</code></pre>
<p>Then, we will need to run our migration to create <code>personal_access_tokens</code> table, which will be used by Sanctum to save access tokens for the users.</p>
<pre><code class="lang-bash">php artisan migrate
</code></pre>
<p>Since we are using Sanctum for our SPA, we need to make sure that our HTTP request pass through Sanctum middleware. Configure <code>api</code> middleware group in <code>app/Http/Kernel.php</code> to use Sanctum middleware.</p>
<pre><code class="lang-php"><span class="hljs-comment">// FILE: app/Http/Kernel.php</span>

<span class="hljs-keyword">use</span> <span class="hljs-title">Laravel</span>\<span class="hljs-title">Sanctum</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Middleware</span>\<span class="hljs-title">EnsureFrontendRequestsAreStateful</span>;

<span class="hljs-keyword">protected</span> $middlewareGroups = [
  ...
  <span class="hljs-string">'api'</span> =&gt; [
      EnsureFrontendRequestsAreStateful::class, <span class="hljs-comment">// Add &amp; import this class</span>
      <span class="hljs-string">'throttle:60,1'</span>,
      \Illuminate\Routing\Middleware\SubstituteBindings::class,
  ],
];
</code></pre>
<p>Next, let's configure domains for our SPA. To make sure our SPA works with Sanctum, set the appropriate values for <code>SESSION_DOMAIN</code> and <code>SANCTUM_STATEFUL_DOMAINS</code> inside <code>.env</code> file at the root of our application.</p>
<pre><code class="lang-plaintext">SESSION_DOMAIN="localhost"
SANCTUM_STATEFUL_DOMAINS="localhost"
</code></pre>
<p>Also, make sure our SPA domain is configured and have set <code>supports_credentials</code> to <code>true</code> in CORS <code>config/cors.php</code>.</p>
<pre><code class="lang-php"><span class="hljs-comment">// FILE: config/cors.php</span>

<span class="hljs-keyword">return</span> [
    ...
    <span class="hljs-string">'paths'</span> =&gt; [<span class="hljs-string">'*'</span>],
    <span class="hljs-string">'allowed_origins'</span> =&gt; [<span class="hljs-string">'http://localhost:3000'</span>],
    <span class="hljs-string">'supports_credentials'</span> =&gt; <span class="hljs-literal">true</span>,
    ...
];
</code></pre>
<p>And our last step should be to use Sanctum auth middleware <code>auth:sanctum</code> to protect our routes like this in <code>routes/api.php</code>.</p>
<pre><code class="lang-php">Route::middleware(<span class="hljs-string">'auth:sanctum'</span>)-&gt;get(<span class="hljs-string">'/user'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Request $request</span>) </span>{
    <span class="hljs-keyword">return</span> $request-&gt;user();
});
</code></pre>
<p>And we are done! Now that we have finished setting up our API backend with Sanctum. Let's proceed for setting up our Nuxt SPA app to use our API.</p>
<h2 id="heading-nuxt-application-setup">Nuxt application setup</h2>
<p>Let's begin by setting up the Nuxt.js app first, and then the Laravel-based API backend using Sanctum. Before that, let me provide you a little information on how to set up your domains to work with the Sanctum's SPA authentication.</p>
<p>Create a new Nuxt.js project by entering the following command in your terminal.</p>
<pre><code class="lang-bash">npx create-nuxt-app your-project-name
</code></pre>
<p>Next, setup will ask you a series of questions, answers them as your preferences.</p>
<pre><code class="lang-bash">? Project name: sanctum-nuxt
? Project description: My fantastic Nuxt.js project
? Author name: Swapnil Bhavsar
? Choose the package manager: Npm
? Choose UI framework: Tailwind CSS
? Choose custom server framework: None (Recommended)
...
</code></pre>
<p>When setup asks for choosing Nuxt.js modules, be sure toggle <code>axios</code> the option, We will need it, so that our Nuxt app can make HTTP requests.</p>
<pre><code class="lang-bash">? Choose Nuxt.js modules (Press &lt;space&gt; to select, &lt;a&gt; to toggle all, &lt;i&gt; to invert selection)
- Axios
</code></pre>
<p>In the final step, choose the <code>Single Page App</code> when setup asks for, choose rendering mode. Of course, you can also choose to render your app in the <code>universal</code> mode. But for the scope of this tutorial, we will deploy our app in the <code>spa</code> mode.</p>
<pre><code class="lang-bash">? Choose rendering mode (Use arrow keys)
- Single Page App
</code></pre>
<h4 id="heading-auth-module">Auth module</h4>
<p>The <a target="_blank" href="https://auth.nuxtjs.org">Auth module</a> is an official package provided by the Nuxt.js community which adds authentication support for the Nuxt.js app. It provides helper objects like <a target="_blank" href="https://auth.nuxtjs.org/api/auth.html"><code>$auth</code></a>, which can be used to log in a user or access an authenticated user.</p>
<p>To allow our app to authenticate with our API backend, we will need to create an auth strategy scheme in the auth section of <code>nuxt.config.js</code>. But before that, we need to install the Auth Module.</p>
<p>Install the Auth module by running the following command in terminal:</p>
<pre><code class="lang-bash">npm install @nuxtjs/auth
</code></pre>
<p>Then, register the auth module in <code>nuxt.config.js</code> like this:</p>
<pre><code class="lang-js">modules: [
  <span class="hljs-string">'@nuxtjs/axios'</span>,
  <span class="hljs-string">'@nuxtjs/auth'</span>
],
<span class="hljs-attr">auth</span>: {
  <span class="hljs-comment">// Options</span>
}
</code></pre>
<p>Before proceeding to configuring the auth module, set <code>baseURL</code> and <code>credentials</code> options for the <code>axios</code> section in <code>nuxt.config.js</code>. Setting <code>credentials: true</code> will include cookies in the HTTP request made to the server.</p>
<pre><code class="lang-js">axios: {
  <span class="hljs-attr">baseURL</span>: <span class="hljs-string">"http://localhost:8000"</span>,
  <span class="hljs-attr">credentials</span>: <span class="hljs-literal">true</span>
},
</code></pre>
<p>Next, configure the auth module in <code>nuxt.config.js</code> to authenticate with our Laravel application endpoints like this:</p>
<pre><code class="lang-js">auth: {
  <span class="hljs-attr">redirect</span>: {
    <span class="hljs-attr">login</span>: <span class="hljs-string">'/login'</span>,
    <span class="hljs-attr">logout</span>: <span class="hljs-string">'/'</span>,
    <span class="hljs-attr">callback</span>: <span class="hljs-string">'/login'</span>,
    <span class="hljs-attr">home</span>: <span class="hljs-string">'/'</span>
  },
  <span class="hljs-attr">strategies</span>: {
    <span class="hljs-attr">local</span>: {
      <span class="hljs-attr">endpoints</span>: {
        <span class="hljs-attr">login</span>: { <span class="hljs-attr">url</span>: <span class="hljs-string">'/login'</span>, <span class="hljs-attr">method</span>: <span class="hljs-string">'post'</span>, <span class="hljs-attr">propertyName</span>: <span class="hljs-literal">false</span> },
        <span class="hljs-attr">user</span>: { <span class="hljs-attr">url</span>: <span class="hljs-string">'/api/user'</span>, <span class="hljs-attr">method</span>: <span class="hljs-string">'get'</span>, <span class="hljs-attr">propertyName</span>: <span class="hljs-literal">false</span> }
      },
      <span class="hljs-attr">tokenRequired</span>: <span class="hljs-literal">false</span>,
      <span class="hljs-attr">tokenType</span>: <span class="hljs-literal">false</span>
    }
  },
  <span class="hljs-attr">localStorage</span>: <span class="hljs-literal">false</span>
},
</code></pre>
<p>After adding the <code>local</code> strategy in the auth section, let's proceed to creating a login page for our Nuxt app.</p>
<h4 id="heading-creating-a-login-page">Creating a login page</h4>
<p>Create a file called <code>login.vue</code> under the <code>pages</code> directory in your Nuxt.js project with the following content. Before loading, it sends a request to <code>/sanctum/csrf-cookie</code> path so that the server could initialize CSRF protection for the application. It also has a login form which uses <code>$auth.loginWith</code> a function provided by the auth module to submit the login form.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex h-screen items-center justify-center"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"loginform"</span> @<span class="hljs-attr">submit.prevent</span>=<span class="hljs-string">"login()"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"w-1/4 mx-auto p-4"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"font-semibold mb-2 text-xl"</span>&gt;</span>
        Login
      <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-4"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"block mb-1 text-sm"</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
          <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span>
          <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span>
          <span class="hljs-attr">class</span>=<span class="hljs-string">"w-full border rounded px-3 py-2"</span>
          <span class="hljs-attr">required</span>
        /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-4"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"block mb-1 text-sm"</span>&gt;</span>Password<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
          <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span>
          <span class="hljs-attr">name</span>=<span class="hljs-string">"password"</span>
          <span class="hljs-attr">class</span>=<span class="hljs-string">"w-full border rounded px-3 py-2"</span>
          <span class="hljs-attr">required</span>
        /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
        <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>
        <span class="hljs-attr">class</span>=<span class="hljs-string">"bg-blue-500 text-white font-semibold py-2 px-10 w-full rounded"</span>
      &gt;</span>
        Login
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
    data() {
      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">error</span>: {},
      };
    },
    mounted() {
      <span class="hljs-comment">// Before loading login page, obtain csrf cookie from the server.</span>
      <span class="hljs-built_in">this</span>.$axios.$get(<span class="hljs-string">'/sanctum/csrf-cookie'</span>);
    },
    <span class="hljs-attr">methods</span>: {
      <span class="hljs-keyword">async</span> login() {
        <span class="hljs-built_in">this</span>.error = {};
        <span class="hljs-keyword">try</span> {
          <span class="hljs-comment">// Prepare form data</span>
          <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">new</span> FormData(<span class="hljs-built_in">this</span>.$refs.loginform);

          <span class="hljs-comment">// Pass form data to `loginWith` function</span>
          <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.$auth.loginWith(<span class="hljs-string">'local'</span>, { <span class="hljs-attr">data</span>: formData });

          <span class="hljs-comment">// Redirect user after login</span>
          <span class="hljs-built_in">this</span>.$router.push({
            <span class="hljs-attr">path</span>: <span class="hljs-string">'/'</span>,
          });
        } <span class="hljs-keyword">catch</span> (err) {
          <span class="hljs-built_in">this</span>.error = err;
          <span class="hljs-comment">// do something with error</span>
        }
      },
    },
  };
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<h4 id="heading-access-authenticated-user">Access authenticated user</h4>
<p>You can access the authenticated user data by using <code>this.$auth.user</code>. For example, create an <code>account.vue</code> the page which will show the user's information like this in the <code>/page</code> directory.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Name: {{ $auth.user.name }}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Name: {{ $auth.user.email }}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
    <span class="hljs-attr">middleware</span>: <span class="hljs-string">'auth'</span>,
  };
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>You can use auth middleware to make sure that your pages are only accessible by authenticated users. You can find more information on using middleware here: <a target="_blank" href="https://auth.nuxtjs.org/guide/middleware.html">https://auth.nuxtjs.org/guide/middleware.html</a></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This is a bare minimum example for you to get started with authentication in Nuxt.js using Laravel Sanctum. I am pretty amazed by the simplicity provided by the Sanctum package over <a target="_blank" href="https://github.com/laravel/passport">Laravel Passport</a> when implementing API authentication for your applications.</p>
<p>This was my first article on my blog, hope you will find it useful. You can ask me questions or send feedback about the article on Twitter <a target="_blank" href="https://twitter.com/swapnil_bhavsar">@swapnil_bhavsar</a>.</p>
]]></content:encoded></item><item><title><![CDATA[How to configure Laravel Cashier with multiple models]]></title><description><![CDATA[I recently worked on a Laravel project which had the requirement of two authenticable models along with separate subscriptions. The project, of course, was using Laravel Cashier to manage user subscriptions.
By default, Laravel Cashier assumes the Ap...]]></description><link>https://swapnil.dev/how-to-configure-laravel-cashier-with-multiple-models</link><guid isPermaLink="true">https://swapnil.dev/how-to-configure-laravel-cashier-with-multiple-models</guid><category><![CDATA[Laravel]]></category><dc:creator><![CDATA[Swapnil Bhavsar]]></dc:creator><pubDate>Sat, 02 Jan 2021 10:19:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1681395606304/9152d3f1-0b41-4c2f-9a36-aa0e49cbf358.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently worked on a <a target="_blank" href="https://laravel.com">Laravel</a> project which had the requirement of two authenticable models along with separate subscriptions. The project, of course, was using <a target="_blank" href="https://laravel.com/docs/8.x/billing">Laravel Cashier</a> to manage user subscriptions.</p>
<p>By default, Laravel Cashier assumes the <code>App\Model\User</code> class as a Billable model. We can configure it to use a different model, but in our case, there were two different models. So, I had to follow a different approach.</p>
<pre><code class="lang-bash">CASHIER_MODEL=App\Models\User
</code></pre>
<p>PS: This will be a long tutorial! I will explain everything from creating models, updating migrations, configuring webhooks, etc.</p>
<p>But if you are rushing, here is your solution, the trick is to set Cashier's billable model at the runtime using the <code>config</code> helper function.</p>
<pre><code class="lang-php">config([<span class="hljs-string">'cashier.model'</span> =&gt; <span class="hljs-string">'App\Models\Seller'</span>]);
</code></pre>
<h2 id="heading-initial-setup">Initial Setup</h2>
<p>Let's start by assuming that our application has two billable models, a <code>User</code>, and <code>Seller</code>. Both models will have subscriptions. There can be multiple ways to use cashier with multiple models, but for simplicity, we are going to store the details of their subscription in separate tables.</p>
<p>Let's start by installing the Cashier package for Stripe first.</p>
<pre><code class="lang-bash">composer require laravel/cashier
</code></pre>
<p>The cashier will use <code>subscriptions</code> and <code>subscription_items</code> tables to store information about the user's subscriptions. Let's publish Cashier's default migrations so that we can take over a look at the table structure.</p>
<pre><code class="lang-bash">php artisan vendor:publish --tag=<span class="hljs-string">"cashier-migrations"</span>
</code></pre>
<p>Now we should have the following files in our <code>database/migrations</code> directory.</p>
<ol>
<li><p><code>2019_05_03_000001_create_customer_columns.php</code></p>
</li>
<li><p><code>2019_05_03_000002_create_subscriptions_table.php</code></p>
</li>
<li><p><code>2019_05_03_000003_create_subscription_items_table.php</code></p>
</li>
</ol>
<p>These files contain schema information about the subscriptions table. Don't worry about them, we will come to these files later.</p>
<h3 id="heading-the-user-model-setup">The <code>User</code> model setup</h3>
<p>First, let's set up our first billable model, <code>User</code> with Cashier. Add <code>Billable</code> trait to our first billable model which at <code>App\Models\User</code>.</p>
<pre><code class="lang-php"><span class="hljs-keyword">use</span> <span class="hljs-title">Laravel</span>\<span class="hljs-title">Cashier</span>\<span class="hljs-title">Billable</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Authenticatable</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">Billable</span>;
}
</code></pre>
<p>This user model is going to have its subscription information stored in the <code>subscriptions</code> table.</p>
<p>Next, let's create our second billable model &amp; add migrations for it.</p>
<h3 id="heading-the-seller-model-setup">The <code>Seller</code> model setup</h3>
<p>Our <code>Seller</code> model will have subscriptions like the <code>User</code> model. But, we need to set up a few more things than just adding a <code>Billable</code> trait to our model. We will need to add migrations, configure auth guard, etc. for the <code>Seller</code> model.</p>
<p>Along with the <code>Seller</code> model, we will create two more models, <code>SellerSubscription</code> &amp; <code>SellerSubscriptionItem</code>. The <code>SellerSubscription</code> will hold the subscription information for the <code>Seller</code> model, and the <code>SellerSubscriptionItem</code> model will be responsible for holding the <a target="_blank" href="https://stripe.com/docs/billing/subscriptions/multiplan">Multiplan Subscriptions</a>.</p>
<p>In short, we are going to need the following models &amp; tables for our <code>Seller</code> model.</p>
<ol>
<li><p>The <code>Seller</code> model with <code>sellers</code> table.</p>
</li>
<li><p>The <code>SellerSubscription</code> model with <code>seller_subscriptions</code> table.</p>
</li>
<li><p>The <code>SellerSubscriptionItem</code> model with <code>seller_subscription_items</code> table.</p>
</li>
</ol>
<p>Let's start by generating our model using the following artisan command. Also, generate model &amp; migrations files by adding the <code>-m</code> flag to our command.</p>
<pre><code class="lang-bash">php artisan make:model Seller -m
</code></pre>
<p>It should generate these two files at the following locations.</p>
<ol>
<li><p><code>Seller.php</code> (In <code>/app/models</code> directory) - The Seller model</p>
</li>
<li><p><code>2021_XX_XX_XXXXXX_create_sellers_table.php</code> (In <code>/database/migrations</code> directory) - The migration file</p>
</li>
</ol>
<p>Now, let's set up our <code>Seller</code> model. Just like the <code>User</code> model we need to add the <code>Billable</code> trait to our <code>Seller</code> model sitting at <code>App\Models\Seller</code>.</p>
<pre><code class="lang-php"><span class="hljs-keyword">use</span> <span class="hljs-title">Laravel</span>\<span class="hljs-title">Cashier</span>\<span class="hljs-title">Billable</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Seller</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Authenticatable</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">Billable</span>;
}
</code></pre>
<p>In the migration file (<code>2021_XX_XX_XXXXXX_create_sellers_table.php</code>) for creating the <code>sellers</code> table, add the following schema content. We will also bring columns we got from <code>2019_05_03_000001_create_customer_columns.php</code> after publishing Cashier's default migrations.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Schema</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Schema</span>\<span class="hljs-title">Blueprint</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Migrations</span>\<span class="hljs-title">Migration</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CreateSellersTable</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Migration</span>
</span>{
    <span class="hljs-comment">/**
     * Run the migrations.
     *
     * <span class="hljs-doctag">@return</span> void
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">up</span>(<span class="hljs-params"></span>)
    </span>{
        Schema::create(<span class="hljs-string">'sellers'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Blueprint $table</span>) </span>{
            $table-&gt;id();
            $table-&gt;string(<span class="hljs-string">'name'</span>);
            $table-&gt;string(<span class="hljs-string">'email'</span>)-&gt;unique();
            $table-&gt;timestamp(<span class="hljs-string">'email_verified_at'</span>)-&gt;nullable();
            $table-&gt;string(<span class="hljs-string">'password'</span>);
            $table-&gt;rememberToken();
            $table-&gt;timestamps();

            <span class="hljs-comment">// Stripe Cashier's columns</span>
            $table-&gt;string(<span class="hljs-string">'stripe_id'</span>)-&gt;nullable()-&gt;index();
            $table-&gt;string(<span class="hljs-string">'card_brand'</span>)-&gt;nullable();
            $table-&gt;string(<span class="hljs-string">'card_last_four'</span>, <span class="hljs-number">4</span>)-&gt;nullable();
            $table-&gt;timestamp(<span class="hljs-string">'trial_ends_at'</span>)-&gt;nullable();
        });
    }

    <span class="hljs-comment">/**
     * Reverse the migrations.
     *
     * <span class="hljs-doctag">@return</span> void
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">down</span>(<span class="hljs-params"></span>)
    </span>{
        Schema::dropIfExists(<span class="hljs-string">'sellers'</span>);
    }
}
</code></pre>
<p>We are almost finished with our <code>Seller</code> model. But we still need to add seller specific subscriptions model and migration.</p>
<h3 id="heading-the-sellersubscription-model-setup">The <code>SellerSubscription</code> model setup</h3>
<p>By default, subscription information for model <code>App\Models\User</code> will be stored in the <code>subscriptions</code> table. By using the <code>Billable</code> trait we are instructing Laravel, the <code>User</code> model will have a <code>hasMany</code> relation with the <code>Laravel\Cashier\Subscription</code> model. We can confirm that by the <code>ManagesSubscriptions</code> trait.</p>
<p>Here in the <code>Laravel\Cashier\Concerns\ManagesSubscriptions</code> trait, we can see the <code>subscriptions</code> method, which defines <code>hasMany</code> relation with the <code>Laravel\Cashier\Subscription</code> model.</p>
<pre><code class="lang-php"><span class="hljs-keyword">use</span> <span class="hljs-title">Laravel</span>\<span class="hljs-title">Cashier</span>\<span class="hljs-title">Subscription</span>;

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">subscriptions</span>(<span class="hljs-params"></span>)
</span>{
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;hasMany(Subscription::class, <span class="hljs-keyword">$this</span>-&gt;getForeignKey())-&gt;orderBy(<span class="hljs-string">'created_at'</span>, <span class="hljs-string">'desc'</span>);
}
</code></pre>
<p>So, we are going to create a class called <code>SellerSubscription</code> which extends the <code>Laravel\Cashier\Subscription</code> model &amp; inherits its properties.</p>
<p>In your console run the following command to generate the <code>SellerSubscription</code> model &amp; migration.</p>
<pre><code class="lang-bash">php artisan make:model SellerSubscription -m
</code></pre>
<p>This will generate the following files.</p>
<ol>
<li><p><code>SellerSubscription.php</code> (In <code>/app/models</code> directory)</p>
</li>
<li><p><code>2021_XX_XX_XXXXXX_create_seller_subscriptions_table.php</code> (In <code>/database/migrations</code> directory)</p>
</li>
</ol>
<h3 id="heading-the-sellersubscriptionitem-model-setup">The <code>SellerSubscriptionItem</code> model setup</h3>
<p>Our <code>User</code> model has <a target="_blank" href="https://stripe.com/docs/billing/subscriptions/multiplan">Multiplan Subscriptions</a> stored in the <code>subscription_items</code> table. Then why should we leave the <code>Seller</code> model behind? Let's add multi-plan subscriptions functionality to the <code>Seller</code> model by defining a new model called <code>SellerSubscriptionItem</code>.</p>
<p>Let's generate the <code>SellerSubscriptionItem</code> model along with migration by running the following command in the terminal.</p>
<pre><code class="lang-bash">php artisan make:model SellerSubscriptionItem -m
</code></pre>
<p>This command should generate the following files.</p>
<ol>
<li><p><code>SellerSubscriptionItem.php</code> (In <code>/app/models</code> directory)</p>
</li>
<li><p><code>2021_XX_XX_XXXXXX_create_seller_subscription_items_table.php</code> (In <code>/database/migrations</code> directory)</p>
</li>
</ol>
<p>Next, modify the <code>SellerSubscription</code> class slightly to extend <code>Laravel\Cashier\Subscription</code> class. And also define <code>belongsTo</code> relation with the <code>Seller</code> class as well as <code>hasMany</code> relation with the <code>SellerSubscriptionItem</code> class.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Laravel</span>\<span class="hljs-title">Cashier</span>\<span class="hljs-title">Subscription</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Factories</span>\<span class="hljs-title">HasFactory</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SellerSubscription</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Subscription</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">HasFactory</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">owner</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;belongsTo(Seller::class);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">items</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;hasMany(SellerSubscriptionItem::class);
    }
}
</code></pre>
<p>Next, also modify <code>SellerSubscriptionItem</code> class to extend <code>Laravel\Cashier\SubscriptionItem</code> class. And define <code>belongsTo</code> relation with the <code>SellerSubscription</code> class like this.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Laravel</span>\<span class="hljs-title">Cashier</span>\<span class="hljs-title">SubscriptionItem</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Factories</span>\<span class="hljs-title">HasFactory</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SellerSubscriptionItem</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">SubscriptionItem</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">HasFactory</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">subscription</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;belongsTo(SellerSubscription::class);
    }
}
</code></pre>
<p>Now, it's time to take inspiration from Cashier's default migration, and update migrations for <code>seller_subscriptions</code> and <code>seller_subscription_items</code> accordingly.</p>
<p>Update migration <code>2021_XX_XX_XXXXXX_create_seller_subscriptions_table.php</code> for <code>seller_subscriptions</code> table with the following schema structure. Pay attention to referencing key <code>seller_id</code> &amp; modify it according to your custom model.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Migrations</span>\<span class="hljs-title">Migration</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Schema</span>\<span class="hljs-title">Blueprint</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Schema</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CreateSellerSubscriptionsTable</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Migration</span>
</span>{
    <span class="hljs-comment">/**
     * Run the migrations.
     *
     * <span class="hljs-doctag">@return</span> void
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">up</span>(<span class="hljs-params"></span>)
    </span>{
        Schema::create(<span class="hljs-string">'seller_subscriptions'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Blueprint $table</span>) </span>{
            $table-&gt;bigIncrements(<span class="hljs-string">'id'</span>);
            $table-&gt;unsignedBigInteger(<span class="hljs-string">'seller_id'</span>);
            $table-&gt;string(<span class="hljs-string">'name'</span>);
            $table-&gt;string(<span class="hljs-string">'stripe_id'</span>);
            $table-&gt;string(<span class="hljs-string">'stripe_status'</span>);
            $table-&gt;string(<span class="hljs-string">'stripe_plan'</span>)-&gt;nullable();
            $table-&gt;integer(<span class="hljs-string">'quantity'</span>)-&gt;nullable();
            $table-&gt;timestamp(<span class="hljs-string">'trial_ends_at'</span>)-&gt;nullable();
            $table-&gt;timestamp(<span class="hljs-string">'ends_at'</span>)-&gt;nullable();
            $table-&gt;timestamps();

            $table-&gt;index([<span class="hljs-string">'seller_id'</span>, <span class="hljs-string">'stripe_status'</span>]);
        });
    }

    <span class="hljs-comment">/**
     * Reverse the migrations.
     *
     * <span class="hljs-doctag">@return</span> void
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">down</span>(<span class="hljs-params"></span>)
    </span>{
        Schema::dropIfExists(<span class="hljs-string">'seller_subscriptions'</span>);
    }
}
</code></pre>
<p>Next, also update migration <code>2021_XX_XX_XXXXXX_create_seller_subscription_items_table.php</code> for <code>seller_subscription_items</code> with the following schema structure. And also pay attention to referencing key <code>seller_subscription_id</code> &amp; modify it according to your custom subscription item model.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Schema</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Schema</span>\<span class="hljs-title">Blueprint</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Migrations</span>\<span class="hljs-title">Migration</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CreateSellerSubscriptionItemsTable</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Migration</span>
</span>{
    <span class="hljs-comment">/**
     * Run the migrations.
     *
     * <span class="hljs-doctag">@return</span> void
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">up</span>(<span class="hljs-params"></span>)
    </span>{
        Schema::create(<span class="hljs-string">'seller_subscription_items'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Blueprint $table</span>) </span>{
            $table-&gt;bigIncrements(<span class="hljs-string">'id'</span>);
            $table-&gt;unsignedBigInteger(<span class="hljs-string">'seller_subscription_id'</span>);
            $table-&gt;string(<span class="hljs-string">'stripe_id'</span>)-&gt;index();
            $table-&gt;string(<span class="hljs-string">'stripe_plan'</span>);
            $table-&gt;integer(<span class="hljs-string">'quantity'</span>);
            $table-&gt;timestamps();

            <span class="hljs-comment">// Short key name to support 64 character limit- http://dev.mysql.com/doc/refman/5.5/en/identifiers.html</span>
            $table-&gt;unique([<span class="hljs-string">'seller_subscription_id'</span>, <span class="hljs-string">'stripe_plan'</span>], <span class="hljs-string">'seller_subscription_id_stripe_plan_unique'</span>);
        });
    }

    <span class="hljs-comment">/**
     * Reverse the migrations.
     *
     * <span class="hljs-doctag">@return</span> void
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">down</span>(<span class="hljs-params"></span>)
    </span>{
        Schema::dropIfExists(<span class="hljs-string">'seller_subscription_items'</span>);
    }
}
</code></pre>
<p>After defining the <code>SellerSubscription</code> and <code>SellerSubscriptionItem</code> models, define a <code>hasMany</code> relation by adding the <code>subscriptions</code> method on the <code>Seller</code> class.</p>
<p>And finally, run the migration command to create/update tables in the database.</p>
<pre><code class="lang-php">php artisan migrate
</code></pre>
<h3 id="heading-the-seller-model">The <code>Seller</code> model</h3>
<p>Now, modify the <code>Seller</code> model to override the <code>subscriptions</code> relation coming from the <code>Billable</code> trait. Instead of defining a relationship between the <code>Laravel\Cashier\Subscription</code> class, define it with <code>App\Models\SellerSubscription</code>.</p>
<p>Your finished seller model should look like this.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Laravel</span>\<span class="hljs-title">Cashier</span>\<span class="hljs-title">Billable</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Model</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Factories</span>\<span class="hljs-title">HasFactory</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Seller</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">HasFactory</span>, <span class="hljs-title">Billable</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">subscriptions</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;hasMany(SellerSubscription::class)-&gt;orderBy(<span class="hljs-string">'created_at'</span>, <span class="hljs-string">'desc'</span>);
    }
}
</code></pre>
<p>Next, let's set up Stripe webhooks for the <code>Seller</code> model. By default, the cashier will use the <code>/stripe/webhook</code> route to handle Stripe webhooks for the default configured model.</p>
<h3 id="heading-webhooks-for-the-seller-model">Webhooks for the <code>Seller</code> model.</h3>
<p>Stripe can notify your application in case the customer's payment method declined and many such events. We need to ensure that our application is handling it. The Cashier package makes it easy by using the <code>/stripe/webhook</code> route to handle these events.</p>
<p>By default, it handles these events for the default configured model <code>App\Models\User</code>. In our case, we should register a different route the handle Stripe webhooks for our custom model.</p>
<p>First, generate a controller called <code>SellerWebhookController</code> using artisan command.</p>
<pre><code class="lang-bash">php artisan make:controller SellerWebhookController
</code></pre>
<p>Next, update <code>SellerWebhookController</code> by extending <code>Laravel\Cashier\Http\Controllers\WebhookController</code> class. And, also modify the <code>getUserByStripeId</code> method to use our custom model <code>Seller</code>.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Controllers</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">Seller</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Laravel</span>\<span class="hljs-title">Cashier</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Controllers</span>\<span class="hljs-title">WebhookController</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SellerWebhookController</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">WebhookController</span>
</span>{
    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getUserByStripeId</span>(<span class="hljs-params">$stripeId</span>)
    </span>{
        <span class="hljs-keyword">return</span> Seller::where(<span class="hljs-string">'stripe_id'</span>, $stripeId)-&gt;first();
    }
}
</code></pre>
<p>After controller, register a new route SellerWebhookController to handle webhook events coming from the Stripe.</p>
<pre><code class="lang-php"><span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Controllers</span>\<span class="hljs-title">SellerWebhookController</span>;

<span class="hljs-comment">// Route for handling Stripe events</span>
Route::post(<span class="hljs-string">'/stripe/seller/webhook'</span>, [SellerWebhookController::class, <span class="hljs-string">'handleWebhook'</span>]);
</code></pre>
<p>Next, in your Stripe control panel, you should enable the following webhooks for URL <code>https://{</code><a target="_blank" href="http://yourapplication.com"><code>yourapplication.com</code></a><code>}/stripe/seller/webhook</code>.</p>
<ul>
<li><p><code>customer.subscription.updated</code> - When subscription is updated</p>
</li>
<li><p><code>customer.subscription.deleted</code> - When subscription is cancelled/deleted.</p>
</li>
<li><p><code>customer.updated</code> - When customer's information updated.</p>
</li>
<li><p><code>customer.deleted</code> - When a customer is deleted.</p>
</li>
<li><p><code>invoice.payment_action_required</code> - When action is required for the payment method. Typically, when the customer's card is declined.</p>
</li>
</ul>
<p>That's it, our application is now ready to handle Stripe webhooks for the custom billable model <code>Seller</code>.</p>
<h2 id="heading-configure-auth-guards-andamp-providers">Configure auth guards &amp; providers</h2>
<p>In our application, sellers can authenticate themselves. So it makes sense to configure the <code>Seller</code> model to take advantage of Laravel's authentication functionality.</p>
<p>Start by registering a new User provider in the <code>providers</code> array in <code>config/auth.php</code> for our billable model <code>Seller</code> and also register a new guard in the <code>guards</code> array in <code>config/auth.php</code> file.</p>
<pre><code class="lang-php"><span class="hljs-string">'guards'</span> =&gt; [
        <span class="hljs-comment">// ...</span>

        <span class="hljs-string">'seller'</span> =&gt; [
            <span class="hljs-string">'driver'</span> =&gt; <span class="hljs-string">'session'</span>,
            <span class="hljs-string">'provider'</span> =&gt; <span class="hljs-string">'sellers'</span>,
        ],
    <span class="hljs-comment">// ...</span>
],

<span class="hljs-comment">// ...</span>

<span class="hljs-string">'providers'</span> =&gt; [
        <span class="hljs-comment">// ...</span>

        <span class="hljs-string">'sellers'</span> =&gt; [
            <span class="hljs-string">'driver'</span> =&gt; <span class="hljs-string">'eloquent'</span>,
            <span class="hljs-string">'model'</span> =&gt; App\Models\Seller::class,
        ],
    <span class="hljs-comment">// ...</span>
]
</code></pre>
<p>After this, we should able to retrieve our authenticated seller by using the <code>$request-&gt;user('seller')</code> helper.</p>
<h2 id="heading-uses">Uses</h2>
<p>With all the setup, we are finally ready to use our <code>Seller</code> model with the Cashier. Now we should be able to retrieve our authenticated seller using <code>$request-&gt;user('seller')</code> and override Cashier's default model using <code>config</code> helper.</p>
<pre><code class="lang-php"><span class="hljs-comment">// retrieve authenticated seller</span>
$seller = $request-&gt;user(<span class="hljs-string">'seller'</span>);

<span class="hljs-comment">// override cashiers default model at the runtime</span>
config([<span class="hljs-string">'cashier.model'</span> =&gt; <span class="hljs-string">'App\Models\Seller'</span>]);
</code></pre>
<p>Let's see it, how we can use it in actual code.</p>
<h3 id="heading-creating-a-new-subscription">Creating a new Subscription</h3>
<p>When creating a new subscription for our custom model <code>Seller</code>, retrieve it first by using the <code>$request-&gt;user('seller')</code> helper. And then set the cashier billable model to <code>Seller</code> at runtime using <code>config</code> helper.</p>
<pre><code class="lang-php"><span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Request</span>;

Route::post(<span class="hljs-string">'/seller/subscribe'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Request $request</span>) </span>{
    config([<span class="hljs-string">'cashier.model'</span> =&gt; <span class="hljs-string">'App\Models\Seller'</span>]);

    $request-&gt;user(<span class="hljs-string">'seller'</span>)-&gt;newSubscription(
        <span class="hljs-string">'default'</span>, <span class="hljs-string">'price_premium'</span>
    )-&gt;create($request-&gt;paymentMethodId);

    <span class="hljs-comment">// ...</span>
});
</code></pre>
<h3 id="heading-retrieving-stripe-customer">Retrieving Stripe Customer</h3>
<p>When we need to retrieve a customer using Stripe ID, we should use <code>Cashier::findBillable()</code>. But before that don't forget to set Cashier's billable model using <code>config</code> helper.</p>
<pre><code class="lang-php"><span class="hljs-keyword">use</span> <span class="hljs-title">Laravel</span>\<span class="hljs-title">Cashier</span>\<span class="hljs-title">Cashier</span>;

config([<span class="hljs-string">'cashier.model'</span> =&gt; <span class="hljs-string">'App\Models\Seller'</span>]);

$seller = Cashier::findBillable($stripeId);
</code></pre>
<h3 id="heading-stripe-billing-portal">Stripe Billing Portal</h3>
<p>If you are using Stripe's <a target="_blank" href="https://stripe.com/docs/billing/subscriptions/customer-portal">billing portal</a> in your application, you can redirect our custom <code>Seller</code> model to his billing portal like this.</p>
<pre><code class="lang-php"><span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Request</span>;

Route::get(<span class="hljs-string">'/billing-portal'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Request $request</span>) </span>{
    <span class="hljs-comment">// Override cashier's default model in case</span>
    config([<span class="hljs-string">'cashier.model'</span> =&gt; <span class="hljs-string">'App\Models\Seller'</span>]);

    <span class="hljs-comment">// Retrive authenticated seller</span>
    $seller = $request-&gt;user(<span class="hljs-string">'seller'</span>);

    <span class="hljs-keyword">return</span> $seller-&gt;redirectToBillingPortal();
});
</code></pre>
<p>That's it! These are a few ways to use multiple models with the Cashier package. You can find more information about the Cashier in the <a target="_blank" href="https://laravel.com/docs/8.x/billing">documentation</a>.</p>
<h2 id="heading-summary">Summary</h2>
<p><a target="_blank" href="https://laravel.com/docs/8.x/billing">Laravel Cashier</a> is a wonderful package for managing subscriptions in Laravel. Of course, there can be other ways to use Cashier with multiple models. This is one of the approaches you can use when you have more than two billable models.</p>
<p>Anyway, this was one long article, and thank you for going through the article! If you have questions about the article, then hit me up on Twitter <a target="_blank" href="https://twitter.com/swapnil_bhavsar">@swapnil_bhavsar</a>.</p>
<p>PS: Here is the source code for the project on GitHub - <a target="_blank" href="https://github.com/IamSwap/laravel-cashier-multiple-models">https://github.com/IamSwap/laravel-cashier-multiple-models</a></p>
]]></content:encoded></item></channel></rss>