<?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: Amr Abumady</title>
    <description>The latest articles on DEV Community by Amr Abumady (@clientnshield).</description>
    <link>https://dev.to/clientnshield</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3833542%2F55ff19dc-639d-4578-816b-b3cd496d8acb.png</url>
      <title>DEV Community: Amr Abumady</title>
      <link>https://dev.to/clientnshield</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/clientnshield"/>
    <language>en</language>
    <item>
      <title>Clickjacking Still Targets Approval Buttons: A Checklist for Login Teams</title>
      <dc:creator>Amr Abumady</dc:creator>
      <pubDate>Sat, 24 Oct 2026 06:36:17 +0000</pubDate>
      <link>https://dev.to/clientnshield/clickjacking-still-targets-approval-buttons-a-checklist-for-login-teams-1218</link>
      <guid>https://dev.to/clientnshield/clickjacking-still-targets-approval-buttons-a-checklist-for-login-teams-1218</guid>
      <description>&lt;p&gt;Somewhere on your site there is a button that says &lt;strong&gt;Approve&lt;/strong&gt;. It might confirm a sign-in on a new device, let a third-party app read an account, or authorize a payment. Clickjacking makes the person press it for a reason they never meant to.&lt;/p&gt;

&lt;p&gt;The defenses have existed for over a decade. What has changed is the target. The approval step after a login is often the most consequential click on a site, and it is the one teams most often leave unprotected.&lt;/p&gt;

&lt;p&gt;I'm building &lt;a href="https://clientn.com/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=clickjacking" rel="noopener noreferrer"&gt;ClientN&lt;/a&gt;, so I have a stake in approval flows. The checklist below applies whatever you use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send the frame policy as a header
&lt;/h2&gt;

&lt;p&gt;In October 2013 the IETF published RFC 7034, an informational document about the &lt;code&gt;X-Frame-Options&lt;/code&gt; header. It tells the browser whether other sites may frame your page. The attacker's page cannot change the header your server sends.&lt;/p&gt;

&lt;p&gt;Its successor is the &lt;code&gt;frame-ancestors&lt;/code&gt; directive from CSP. For approval pages, a narrow rule is usually right:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep &lt;code&gt;X-Frame-Options&lt;/code&gt; alongside it for older browsers. Two details catch teams out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The CSP3 specification says &lt;code&gt;frame-ancestors&lt;/code&gt; is not supported in a &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; element, so the policy must arrive as an HTTP header.&lt;/li&gt;
&lt;li&gt;Check every response that shows the approval, not only the main route. A retry page or a self-reloading confirmation can be a separate response with its own headers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Frame-busting JavaScript is at best a second layer. It runs inside the framed page, and the framing page controls that context.&lt;/p&gt;

&lt;h2&gt;
  
  
  The variant headers do not stop
&lt;/h2&gt;

&lt;p&gt;In January 2025, BankInfoSecurity reported on DoubleClickjacking. According to that report, the technique uses two windows instead of a frame. A decoy window closes on the first click and reveals a sensitive page underneath, and the second click lands on that page. The researcher, Paulos Yibelo, showed proof-of-concept account takeovers on Salesforce, Slack and Shopify. The report says popular defenses such as Content Security Policy and SameSite cookies fail against it.&lt;/p&gt;

&lt;p&gt;So a header does not cover window switching. Two things are worth testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A second, deliberate step that a decoy page cannot complete, such as a passkey or device PIN prompt.&lt;/li&gt;
&lt;li&gt;A short delay before a high-risk button becomes active.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are design choices to test, not guarantees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inventory&lt;/strong&gt; every page where one click approves something: device sign-ins, consent screens, payment confirmations, recovery-code reveals, account deletion, and login-method changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Send the policy&lt;/strong&gt; on those responses, then test it. Load the page in a frame on another origin and confirm the browser refuses to render it. From a terminal: &lt;code&gt;curl -sI https://your.site/approve | grep -iE 'frame-ancestors|x-frame-options'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show what is approved&lt;/strong&gt;: the requesting app or device, and the access it asks for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Require a second step&lt;/strong&gt; for anything that grants access or changes the account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log every approval&lt;/strong&gt;: the app or device, the time, whether a second step completed, and which page the request came from.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Where sign-in fits
&lt;/h2&gt;

&lt;p&gt;An approval is tied to an account, and the account is usually tied to a stored email address. If the approval happened on the person's own device, with a key that stays there, a site could confirm that the same person approved a request without storing who that person is. That is a design direction, not a claim that every approval works this way today.&lt;/p&gt;

&lt;p&gt;ClientN explores that split. The &lt;a href="https://clientn.com/security/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=clickjacking" rel="noopener noreferrer"&gt;security page&lt;/a&gt; describes how approvals work, and the &lt;a href="https://clientn.com/docs?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=clickjacking" rel="noopener noreferrer"&gt;docs&lt;/a&gt; show how to start.&lt;/p&gt;




&lt;p&gt;Originally published on the &lt;a href="https://clientn.com/blog/clickjacking-still-targets-approval-buttons" rel="noopener noreferrer"&gt;ClientN blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>authentication</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Founding price: what the $5 first year of ClientN membership includes</title>
      <dc:creator>Amr Abumady</dc:creator>
      <pubDate>Sat, 17 Oct 2026 06:32:57 +0000</pubDate>
      <link>https://dev.to/clientnshield/founding-price-what-the-5-first-year-of-clientn-membership-includes-2gii</link>
      <guid>https://dev.to/clientnshield/founding-price-what-the-5-first-year-of-clientn-membership-includes-2gii</guid>
      <description>&lt;p&gt;I'm building ClientN, so this is a product post. It only covers what is live today.&lt;/p&gt;

&lt;p&gt;Free sign-in stays free. You need an email address and a passkey. There is no card and no app. Each website you sign in to gets its own anonymous CN- ID, so the same person looks like a different user to each site.&lt;/p&gt;

&lt;p&gt;Membership is optional. The founding price is $5 for your first year if you join before 2027-01-01 00:00 UTC. After that it is $40 a year, renews automatically, and you can cancel at any time. Here is what it includes today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Private messages from the sites you have signed in to. They arrive in your ClientN inbox, and the site never sees your email address.&lt;/li&gt;
&lt;li&gt;Blocking and reporting any sender, at any time.&lt;/li&gt;
&lt;li&gt;Advanced recovery: recovery codes, plus an email-based waiting path if you lose access.&lt;/li&gt;
&lt;li&gt;The Verified Human badge, if you want it. It needs one ID or passport check from any country, plus a live selfie checked on ClientN's own server. The selfie is deleted after the check. What we keep is an encrypted face pattern and one-way document codes. Websites see only a yes or no: verified unique human.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not ready yet: phone verification and phone recovery. The pricing page lists them as coming soon, and this post does not give a date.&lt;/p&gt;

&lt;p&gt;Payment is handled by Polar, the merchant of record. ClientN does not receive your card number, the last four digits, or a card fingerprint.&lt;/p&gt;

&lt;p&gt;Sign-in security is also live. Every sign-in asks for a fingerprint, face, or device PIN. Same-device sign-ins are tied to the browser that started them, so a forwarded link signs nobody in. QR sign-ins show the requesting device and country, and you pick the matching number from three choices. "This wasn't me" cancels the request. Details are at &lt;a href="https://clientn.com/security" rel="noopener noreferrer"&gt;https://clientn.com/security&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pricing: &lt;a href="https://clientn.com/pricing" rel="noopener noreferrer"&gt;https://clientn.com/pricing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Free account: &lt;a href="https://clientn.com/signup" rel="noopener noreferrer"&gt;https://clientn.com/signup&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Questions are welcome in the comments. If the answer is "not yet", I will say so.&lt;/p&gt;

&lt;p&gt;Verified Human. Still Anonymous.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>security</category>
      <category>webdev</category>
      <category>authentication</category>
    </item>
    <item>
      <title>QR-code login can be phished. Here is how we closed the hole</title>
      <dc:creator>Amr Abumady</dc:creator>
      <pubDate>Fri, 09 Oct 2026 09:20:13 +0000</pubDate>
      <link>https://dev.to/clientnshield/qr-code-login-can-be-phished-here-is-how-we-closed-the-hole-4413</link>
      <guid>https://dev.to/clientnshield/qr-code-login-can-be-phished-here-is-how-we-closed-the-hole-4413</guid>
      <description>&lt;p&gt;QR-code login looks safe. You scan a code, approve on your phone, and the computer is signed in. No password typed, nothing to phish.&lt;/p&gt;

&lt;p&gt;We run &lt;a href="https://clientn.com/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=qr-phishing" rel="noopener noreferrer"&gt;ClientN&lt;/a&gt;, an anonymous sign-in service, and our own QR flow had the same hole most QR logins have. This post is about what that hole was and the four changes we shipped to close it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The attack (QR-jacking)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The attacker opens a real website that uses ClientN and starts a sign-in. The site shows a QR code and a short match code.&lt;/li&gt;
&lt;li&gt;The attacker copies that QR code onto a page they control: a fake "scan to claim your reward" page, a fake support chat, anything.&lt;/li&gt;
&lt;li&gt;The victim scans it. Their phone opens the real ClientN page, on the real domain, with a valid TLS certificate. They see a match code, and the fake page shows the same one, because the attacker copied it.&lt;/li&gt;
&lt;li&gt;The victim approves. The website signs in &lt;strong&gt;the attacker's browser&lt;/strong&gt;, because that is the browser that started the request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nothing was faked on our side. The victim approved a real request, just not one they started. Match codes don't help when the attacker can show the same code. Passkeys don't help either: the passkey proves the victim is the victim, not that the victim is sitting at the computer that asked.&lt;/p&gt;

&lt;p&gt;There was a second, smaller problem. If you were already signed in to ClientN, approving a website was one tap on "Confirm". No fingerprint. Anyone holding your unlocked phone could approve sign-ins.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we changed
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. A fresh passkey for every approval
&lt;/h3&gt;

&lt;p&gt;Approving a website sign-in now always asks for the passkey again, and the WebAuthn challenge is tied to that one sign-in request. Having a ClientN session cookie is no longer enough to approve anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Same-device sign-in returns a one-time code bound to the browser
&lt;/h3&gt;

&lt;p&gt;When the person signs in on the same device, the site redirects to ClientN and ClientN redirects back. This works much like OAuth's PKCE:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the site creates the request, it sends a &lt;code&gt;code_challenge&lt;/code&gt; (a SHA-256 hash of a random secret it keeps).&lt;/li&gt;
&lt;li&gt;After approval, ClientN redirects back to the site's registered return address with a one-time code.&lt;/li&gt;
&lt;li&gt;The site swaps the code at &lt;code&gt;POST /api/v1/sessions/{id}/exchange&lt;/code&gt; and must send the original secret.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The secret only exists in the browser session that started the sign-in. A forwarded link, or a code caught on the way, is useless to anyone else. This is now the default flow; QR is for the "phone for a computer" case only.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The QR flow shows where the request came from
&lt;/h3&gt;

&lt;p&gt;For the cross-device case we can't bind to the browser, so we give the person enough to notice an attack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sites send the visitor's IP and user agent when they create the request. The approval page shows "Chrome on Windows · Egypt", read from a local country database. No IP is sent to a third party.&lt;/li&gt;
&lt;li&gt;If the requester's country differs from the phone's, the page shows a red warning.&lt;/li&gt;
&lt;li&gt;Instead of just reading a match code, the person must &lt;strong&gt;pick&lt;/strong&gt; the code shown on the website out of three. One wrong pick denies the request.&lt;/li&gt;
&lt;li&gt;A "This wasn't me" button cancels the request on the spot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this stops someone who ignores every warning. It does turn "tap approve" into a step where the person has to check something, and a lazy phishing page fails at the number pick.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Account-side signals
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The account page lists recent activity with the country it came from.&lt;/li&gt;
&lt;li&gt;We send an email when a new passkey is added and when an account signs in to a new website for the first time.&lt;/li&gt;
&lt;li&gt;Sign-in request data (IP, user agent, country) is erased after 30 days.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it cost the websites
&lt;/h2&gt;

&lt;p&gt;Two small changes: send &lt;code&gt;end_user_ip&lt;/code&gt; and &lt;code&gt;end_user_agent&lt;/code&gt; when creating a session, and handle the return code with the exchange call. We had one connected site, so the timing was easy. If you are building QR login yourself, do this before you have integrations to migrate.&lt;/p&gt;

&lt;p&gt;Our open-source starter kit has working PHP and Node versions of the full flow, with 88 tests (35 Node, 53 PHP), including "another browser cannot take over a login even if it knows the session id": &lt;a href="https://github.com/clientn/clientn-session-starter" rel="noopener noreferrer"&gt;github.com/clientn/clientn-session-starter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The whole model, in plain words, is on our &lt;a href="https://clientn.com/security?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=qr-phishing" rel="noopener noreferrer"&gt;security page&lt;/a&gt;. You can try both flows on the &lt;a href="https://clientn.com/demo?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=qr-phishing" rel="noopener noreferrer"&gt;demo site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you see a way around any of this, I'd like to hear it in the comments.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>authentication</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Hashed Emails Aren't Anonymous: Use Pairwise IDs Instead</title>
      <dc:creator>Amr Abumady</dc:creator>
      <pubDate>Tue, 06 Oct 2026 06:31:23 +0000</pubDate>
      <link>https://dev.to/clientnshield/hashed-emails-arent-anonymous-use-pairwise-ids-instead-1o2g</link>
      <guid>https://dev.to/clientnshield/hashed-emails-arent-anonymous-use-pairwise-ids-instead-1o2g</guid>
      <description>&lt;p&gt;If your sign-in flow stores &lt;code&gt;sha256(lower(email))&lt;/code&gt; and you call that "anonymised", it isn't. A hashed email is a stable, cross-site identifier: every service that hashes the same address gets the same string, and ad-tech "clean rooms" are built on exactly that join.&lt;/p&gt;

&lt;p&gt;I'm building &lt;a href="https://clientn.com" rel="noopener noreferrer"&gt;ClientN&lt;/a&gt;, so I've spent a lot of time on this question. Here is the short version for developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why hashing doesn't help
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Same input, same output.&lt;/strong&gt; &lt;code&gt;alice@example.com&lt;/code&gt; hashes to the same value on your site, on a retailer's site and in a data broker's table. Matching is a plain &lt;code&gt;JOIN&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The input space is small.&lt;/strong&gt; Emails are guessable. Anyone with a list of addresses can hash them and look yours up — no "reversing" needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Salting per site breaks the join, but you still hold the email.&lt;/strong&gt; You collected it, so it can leak, be subpoenaed or be shared later.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What actually limits linkability: pairwise identifiers
&lt;/h2&gt;

&lt;p&gt;Give each relying site a &lt;em&gt;different&lt;/em&gt; identifier for the same person, derived so that sites cannot correlate them. In ClientN, a user signs in with a passkey (WebAuthn) and each integrated site receives its own anonymous &lt;code&gt;CN-&lt;/code&gt; id. Site A's id for a user tells Site B nothing.&lt;/p&gt;

&lt;p&gt;What the site gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a stable id &lt;strong&gt;for that site only&lt;/strong&gt; — enough for accounts, sessions, rate limits and bans on your own service;&lt;/li&gt;
&lt;li&gt;no email and no password to store, so nothing reusable to leak.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where it fits (and where it doesn't)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It only applies on sites that integrate it — it is not a fix for "all internet privacy".&lt;/li&gt;
&lt;li&gt;You still need your own abuse controls; a per-site id gives you a key to apply them to without collecting identity.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Live demo: &lt;a href="https://clientn.com/demo" rel="noopener noreferrer"&gt;https://clientn.com/demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Node + PHP starter (MIT): &lt;a href="https://github.com/clientn/clientn-session-starter" rel="noopener noreferrer"&gt;https://github.com/clientn/clientn-session-starter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://clientn.com/docs" rel="noopener noreferrer"&gt;https://clientn.com/docs&lt;/a&gt; · developer dashboard: &lt;a href="https://clientn.com/dev" rel="noopener noreferrer"&gt;https://clientn.com/dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Websites are free up to 1,000 logins/month until 2027-03-31. People get a free account (email + passkey) at &lt;a href="https://clientn.com/signup" rel="noopener noreferrer"&gt;https://clientn.com/signup&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Verified Human. Still Anonymous.&lt;/em&gt; — Longer write-up on our blog: &lt;a href="https://clientn.com/blog/hashed-emails-arent-anonymous-how-your-login-becomes-an-ad-id" rel="noopener noreferrer"&gt;https://clientn.com/blog/hashed-emails-arent-anonymous-how-your-login-becomes-an-ad-id&lt;/a&gt;&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>security</category>
      <category>webdev</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Passkey Sign-In for a Small Site Without Storing Emails: The 5 Server Steps</title>
      <dc:creator>Amr Abumady</dc:creator>
      <pubDate>Tue, 29 Sep 2026 06:31:34 +0000</pubDate>
      <link>https://dev.to/clientnshield/passkey-sign-in-for-a-small-site-without-storing-emails-the-5-server-steps-3bo1</link>
      <guid>https://dev.to/clientnshield/passkey-sign-in-for-a-small-site-without-storing-emails-the-5-server-steps-3bo1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update, 9 October 2026:&lt;/strong&gt; the flow got safer. The default is now a same-device sign-in: your server sends a &lt;code&gt;code_challenge&lt;/code&gt;, ClientN sends the browser back to your &lt;code&gt;return_url&lt;/code&gt; with a one-time code, and your server exchanges it with its &lt;code&gt;code_verifier&lt;/code&gt; (&lt;code&gt;POST /api/v1/sessions/{id}/exchange&lt;/code&gt;), so a forwarded link signs nobody in. QR sign-ins still work and now show the person which device and country asked. Every approval needs a fresh passkey check. The starter repo and &lt;a href="https://clientn.com/docs" rel="noopener noreferrer"&gt;the docs&lt;/a&gt; are updated; the steps below describe the QR flow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;I'm building ClientN, so read this as a founder's walkthrough, not a neutral review.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most small sites keep a &lt;code&gt;users&lt;/code&gt; table with an email column only because the login needs one. The email then sits there for years, gets copied into backups, and turns up in breach notices. Passkeys remove the password. The question here is whether you can also stop receiving the email.&lt;/p&gt;

&lt;p&gt;This post walks through the flow in our open-source reference integration, &lt;a href="https://github.com/clientn/clientn-session-starter" rel="noopener noreferrer"&gt;clientn-session-starter&lt;/a&gt; (Node.js + PHP, MIT, with tests). It is &lt;strong&gt;not an SDK&lt;/strong&gt;. You write server code, and the repo shows one complete, tested way to do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What your site gets instead of an email
&lt;/h2&gt;

&lt;p&gt;After the visitor confirms with a passkey on clientn.com, your server receives a &lt;code&gt;clientn_id&lt;/code&gt; (&lt;code&gt;CN-…&lt;/code&gt;). That id stays the same for this person on your site and is different on every other site, so two sites can't match their users by it. You never receive a password or an email address.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five server steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start a session.&lt;/strong&gt; Your server calls &lt;code&gt;POST /api/v1/sessions&lt;/code&gt; with your API key. ClientN returns a session id, a confirm URL, a QR code and a 4-character match code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bind it to this browser.&lt;/strong&gt; Store a pending login tied to the browser: a random token in an HttpOnly cookie, and only its hash in your database. The page shows the QR code and the match code (&lt;code&gt;clientn.js&lt;/code&gt; handles the display).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The person confirms.&lt;/strong&gt; They open the confirm URL (a link on the same device, or the QR code on a phone), check that the match code and site name are right, and confirm with their passkey.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify the signed callback.&lt;/strong&gt; ClientN POSTs a signed callback to your server. Verify the signature over the &lt;em&gt;exact raw bytes&lt;/em&gt;. Check that the callback belongs to a pending login you started. Mark it confirmed in one atomic step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log the right browser in.&lt;/strong&gt; The browser holding the pending cookie polls &lt;em&gt;your&lt;/em&gt; &lt;code&gt;/clientn/status&lt;/code&gt;. Once the login is confirmed, consume it exactly once, create a &lt;strong&gt;fresh&lt;/strong&gt; session id and set your app cookie.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the callback is lost, the status route can ask ClientN directly (&lt;code&gt;GET /api/v1/sessions/{id}&lt;/code&gt;) and verify that signed answer the same way. You can also call &lt;code&gt;POST /api/v1/sessions/{id}/redeliver&lt;/code&gt; within 30 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the flow is shaped like this
&lt;/h2&gt;

&lt;p&gt;A few choices look fussy at first, but each one blocks a real attack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Poll your own server, not ClientN.&lt;/strong&gt; ClientN's public status only says the person tapped Confirm. You are logged in only after &lt;em&gt;your&lt;/em&gt; server has accepted the signed callback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store the browser binding as a hash.&lt;/strong&gt; Another browser that learns the session id still can't take over the login.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consume the login once.&lt;/strong&gt; In the test suite, five parallel polls create exactly one session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat a repeated event as a no-op.&lt;/strong&gt; Callbacks are retried 3 times, 5 seconds apart. Return 2xx as soon as you've recorded the event, and make the same event arriving again do nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both apps are tested against the same list: cross-site start is refused, the API key never reaches the browser, tampered or oversized callbacks are rejected, expired logins are refused, and a forged recovery answer is ignored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Node 22.5+ (built-in node:sqlite, no dependencies)&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;node &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env   &lt;span class="c"&gt;# add your key + secret&lt;/span&gt;
npm start &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;test&lt;/span&gt;

&lt;span class="c"&gt;# PHP 8.1+ with pdo_sqlite and curl&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;php &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; php tests/run.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First register your site at &lt;a href="https://clientn.com/dev" rel="noopener noreferrer"&gt;clientn.com/dev&lt;/a&gt; and verify your domain with a DNS TXT record or a &lt;code&gt;/.well-known/&lt;/code&gt; file. Callbacks are sent over HTTPS on port 443 to your verified domain and don't follow redirects, so test on a real staging subdomain, not localhost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Keys have no scopes yet: one key and secret per site. Rotating them ends any sign-ins in progress.&lt;/li&gt;
&lt;li&gt;The demo apps use an in-memory rate limiter. Use your real shared limiter in production.&lt;/li&gt;
&lt;li&gt;It only helps on sites that integrate it. It won't make the rest of the web private.&lt;/li&gt;
&lt;li&gt;Website plans are free up to 1,000 logins per month until 2027-03-31.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try the flow yourself on the &lt;a href="https://clientn.com/demo" rel="noopener noreferrer"&gt;live demo&lt;/a&gt;, or read the &lt;a href="https://clientn.com/docs" rel="noopener noreferrer"&gt;docs&lt;/a&gt;. If you run a small site, I'd like to hear which step would block you. That's the feedback I'm collecting this month.&lt;/p&gt;

</description>
      <category>passkeys</category>
      <category>security</category>
      <category>webdev</category>
      <category>privacy</category>
    </item>
    <item>
      <title>What Google's €403M and $463M Fines Actually Mean for Your Login Table</title>
      <dc:creator>Amr Abumady</dc:creator>
      <pubDate>Tue, 22 Sep 2026 06:37:57 +0000</pubDate>
      <link>https://dev.to/clientnshield/what-googles-eu403m-and-463m-fines-actually-mean-for-your-login-table-104k</link>
      <guid>https://dev.to/clientnshield/what-googles-eu403m-and-463m-fines-actually-mean-for-your-login-table-104k</guid>
      <description>&lt;p&gt;Google was just fined &lt;strong&gt;€403M by Ireland's DPC&lt;/strong&gt; and &lt;strong&gt;$463M&lt;/strong&gt; in a separate action, both tied to how location and account data was collected and retained (&lt;a href="https://www.irishtimes.com/business/2026/09/21/irish-data-protection-watchdog-fines-google-403m-over-gdpr-breaches/" rel="noopener noreferrer"&gt;Irish Times, 2026-09-21&lt;/a&gt;; &lt;a href="https://abcnews.com/Technology/wireStory/google-hit-463-million-fine-eu-location-data-136616044" rel="noopener noreferrer"&gt;ABC News&lt;/a&gt;). Neither fine is about a breach — it's about &lt;em&gt;retention&lt;/em&gt;: data a company was allowed to collect but kept, correlated, or reused longer than the stated purpose required.&lt;/p&gt;

&lt;p&gt;For small and mid-size sites, the same liability shows up in a much smaller, more boring place: your login table. Most auth systems ask for an email up front and keep it forever, whether or not the user ever comes back. That's a data-retention liability you're taking on for zero product benefit in the first session.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern we've been building around
&lt;/h2&gt;

&lt;p&gt;We've been working on &lt;a href="https://clientn.com" rel="noopener noreferrer"&gt;ClientN&lt;/a&gt;, a free passkey-based sign-in you can drop into a site alongside (or instead of) email/password. Two things about the shape of it are relevant to the fines above, not because ClientN is a GDPR product, but because minimizing what you store is the actual fix, not a bigger cookie banner:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each site gets a &lt;strong&gt;different anonymous ID&lt;/strong&gt; for the same person (&lt;code&gt;CN-xxxxx&lt;/code&gt;), not a shared identifier you could correlate against other sites.&lt;/li&gt;
&lt;li&gt;You never see or store the person's email unless they explicitly opt to share it with you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it — it doesn't replace your GDPR review, but it removes an entire category of data you'd otherwise be sitting on and eventually explaining to a regulator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it
&lt;/h2&gt;

&lt;p&gt;Free for sites up to 1,000 logins/month:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Starter (Node + PHP, MIT licence): &lt;a href="https://github.com/clientn/clientn-session-starter" rel="noopener noreferrer"&gt;https://github.com/clientn/clientn-session-starter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://clientn.com/docs" rel="noopener noreferrer"&gt;https://clientn.com/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live demo: &lt;a href="https://clientn.com/demo" rel="noopener noreferrer"&gt;https://clientn.com/demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dashboard: &lt;a href="https://clientn.com/dev" rel="noopener noreferrer"&gt;https://clientn.com/dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Curious how other devs are thinking about minimizing account-data retention post-fines like these — what's in your login table today that doesn't need to be?&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>webdev</category>
      <category>security</category>
      <category>authentication</category>
    </item>
    <item>
      <title>What the Revolut Breach Says About Storing Identity Documents Forever</title>
      <dc:creator>Amr Abumady</dc:creator>
      <pubDate>Sun, 13 Sep 2026 06:34:33 +0000</pubDate>
      <link>https://dev.to/clientnshield/what-the-revolut-breach-says-about-storing-identity-documents-forever-e8n</link>
      <guid>https://dev.to/clientnshield/what-the-revolut-breach-says-about-storing-identity-documents-forever-e8n</guid>
      <description>&lt;p&gt;Revolut confirmed this week that it sent customer passport scans and transaction records to attackers who impersonated a government agency with a forged legal request (&lt;a href="https://www.reuters.com/legal/litigation/revolut-confirms-sensitive-customer-data-breach-falling-fake-government-requests-2026-09-12/" rel="noopener noreferrer"&gt;Reuters, Sept 12&lt;/a&gt;). No server was hacked. No password was cracked. A human on the compliance side looked at a request that read like it came from law enforcement, and handed over the file.&lt;/p&gt;

&lt;p&gt;This is the failure mode that's easy to miss when we talk about "data breaches." We spend enormous engineering effort on encryption at rest, encryption in transit, access logging, and SOC 2 audits, and then the actual leak happens because someone approved a request that looked legitimate enough. The data was sitting there, complete and exportable, waiting for exactly the right piece of social engineering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The root problem isn't the process failure. It's that the data existed to be exported at all.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most identity verification today follows the same shape: a site asks for a passport or ID scan, stores it (or a vendor stores it on the site's behalf), and keeps it indefinitely "in case we need it again" (for compliance, for support disputes, for re-verification). Every one of those stores is a future Revolut. The attacker doesn't need a zero-day; they need one convincing forged letter and one tired employee on a Friday.&lt;/p&gt;

&lt;p&gt;We built ClientN around a different assumption: verification and storage don't have to be the same step.&lt;/p&gt;

&lt;p&gt;When someone verifies as a real, unique human on ClientN, the ID/passport check and the face match happen once, and the raw documents aren't retained afterward: what persists is a cryptographic attestation, not the image. Each site the person then connects to gets a different anonymous ID (a &lt;code&gt;CN-&lt;/code&gt; handle), scoped to that site, with no shared identifier a second party could correlate across services or exfiltrate in one shot. There's no central vault of passport scans sitting behind a support ticket queue for someone to social-engineer their way into, because the vault doesn't exist after the initial check is done.&lt;/p&gt;

&lt;p&gt;This is a trade-off, not a magic fix. A site that genuinely needs an auditable copy of your ID for its own regulatory reasons (say, a bank) still needs to hold that copy. ClientN doesn't solve that problem, and it's not trying to be a KYC vendor for regulated finance. What it solves is the far more common case: sites that ask for identity verification purely to prove "this is one real human, not a bot or a Sybil farm," and then hold onto sensitive documents they never technically needed to keep. Passkey login for session security, a one-time Sybil-resistance check, an anonymous per-site ID, and nothing left over that a forged government letter could unlock.&lt;/p&gt;

&lt;p&gt;If you're building anything that needs "prove you're a real, unique person" without needing "collect and retain their government ID forever," the demo is at &lt;a href="https://clientn.com/demo" rel="noopener noreferrer"&gt;clientn.com/demo&lt;/a&gt; and integration docs are at &lt;a href="https://clientn.com/dev" rel="noopener noreferrer"&gt;clientn.com/dev&lt;/a&gt;. Verified Human accounts start at $1/year during the launch window: &lt;a href="https://clientn.com/signup" rel="noopener noreferrer"&gt;clientn.com/signup&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verified Human. Still Anonymous.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>security</category>
      <category>identity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why websites replaced passwords with sign-in codes — and what comes after</title>
      <dc:creator>Amr Abumady</dc:creator>
      <pubDate>Fri, 04 Sep 2026 09:23:24 +0000</pubDate>
      <link>https://dev.to/clientnshield/why-websites-replaced-passwords-with-sign-in-codes-and-what-comes-after-455j</link>
      <guid>https://dev.to/clientnshield/why-websites-replaced-passwords-with-sign-in-codes-and-what-comes-after-455j</guid>
      <description>&lt;p&gt;There's a recurring question on Hacker News that keeps resurfacing: &lt;em&gt;why do so many websites now email you a sign-in code instead of asking for a password?&lt;/em&gt; It's a fair question, and the answer says a lot about where web authentication is heading — and about a gap almost nobody is solving.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version: passwords were always the wrong primitive
&lt;/h2&gt;

&lt;p&gt;A password is a shared secret. You know it, the site stores a hash of it, and every breach is a race between their hashing and someone's GPU. Email codes ("magic codes") sidestep the stored-secret problem: the site never keeps anything you'd mind leaking, and the proof of identity is "you can read this inbox right now." That's genuinely better than a reused password. It's also why they spread so fast — they're cheap to bolt on and they kill the top support ticket ("I forgot my password").&lt;/p&gt;

&lt;p&gt;But a code in your inbox is still a &lt;em&gt;bearer&lt;/em&gt; proof. Anyone who can read that inbox — a forwarded email, a hijacked mail account, a shoulder-surfed screen — can sign in as you. It moves the weak link from the password to the mailbox; it doesn't remove it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passkeys are the actual fix
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://passkeys.dev/" rel="noopener noreferrer"&gt;Passkeys&lt;/a&gt; (WebAuthn discoverable credentials) replace the shared secret with public-key cryptography bound to your device and unlocked by your fingerprint or face. The site stores a public key; the private key never leaves your hardware. There's nothing in the database worth stealing, nothing to phish, and nothing to type. This is the endpoint the "sign-in code" trend is stumbling toward: no password, no bearer token sitting in an inbox.&lt;/p&gt;

&lt;p&gt;If you build web apps, this is the part worth internalizing: the industry has quietly agreed that the login secret should not be something a human types or an attacker can copy. The only debate left is the packaging.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap nobody talks about: "who" vs. "unique human"
&lt;/h2&gt;

&lt;p&gt;Here's what neither passwords nor magic codes nor even passkeys solve on their own: &lt;strong&gt;is this a real, unique person — without me having to collect their identity?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most sites answer that by hoarding data. They demand your email, your phone, sometimes your ID, and they store all of it so they can de-duplicate accounts and fight bots and Sybil attacks. You end up with the worst of both worlds: the site carries a breach liability it never wanted, and you hand over identifying data to every service you touch.&lt;/p&gt;

&lt;p&gt;That's the problem we've been building &lt;a href="https://clientn.com" rel="noopener noreferrer"&gt;ClientN&lt;/a&gt; around. The idea is to split the two jobs that authentication usually fuses together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prove you're a unique, real human — once.&lt;/strong&gt; We verify a person with a passkey plus a layered "Verified Human" check (an email code, a card check, and an optional ID/passport + selfie face-match done on our own servers, encrypted at rest, never shared).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give each site a &lt;em&gt;different&lt;/em&gt; anonymous identifier for that person.&lt;/strong&gt; A site sees a stable &lt;code&gt;CN-…&lt;/code&gt; id that's unique to &lt;em&gt;that site&lt;/em&gt; and reveals nothing else. The same human logging into two sites gets two unrelated ids, so the sites can't correlate you — but neither can be fooled into thinking one person is a thousand.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The login flow itself is not an OAuth redirect that leaks your identity to a broker. The site asks ClientN for a one-time session; you confirm on &lt;code&gt;clientn.com&lt;/code&gt; with your passkey ("Do you want to sign in to &lt;em&gt;example.com&lt;/em&gt;?"); we send the site back a signed, per-site anonymous id. The site gets "verified, unique, human" without ever getting &lt;em&gt;you&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verified Human. Still Anonymous.
&lt;/h2&gt;

&lt;p&gt;That's the whole thesis in four words. The sign-in-code trend is a symptom of an industry that knows passwords are finished but hasn't decided what "identity" should mean online. Our bet is that the right answer is &lt;strong&gt;less&lt;/strong&gt; identity, cryptographically proven — not more of it, stored in more databases.&lt;/p&gt;

&lt;p&gt;If you want to see it working, there's a live demo forum whose only login is "Sign in with ClientN": &lt;a href="https://clientn.com/demo" rel="noopener noreferrer"&gt;https://clientn.com/demo&lt;/a&gt;. If you run a site and want to add it, the integration is a single &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag plus one signed callback — docs at &lt;a href="https://clientn.com/dev" rel="noopener noreferrer"&gt;https://clientn.com/dev&lt;/a&gt;. And if you just want your own Verified Human account, we're in early access at a $1/year launch price: &lt;a href="https://clientn.com/signup" rel="noopener noreferrer"&gt;https://clientn.com/signup&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Passwords are ending. The interesting question is what we replace them with — and whether we finally stop treating "prove you're human" as an excuse to collect everything about you.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>security</category>
      <category>webdev</category>
      <category>authentication</category>
    </item>
    <item>
      <title>ClientN Shield: Privacy-First Authentication for the Modern Web</title>
      <dc:creator>Amr Abumady</dc:creator>
      <pubDate>Thu, 19 Mar 2026 13:04:32 +0000</pubDate>
      <link>https://dev.to/clientnshield/clientn-shield-privacy-first-authentication-for-the-modern-web-2meh</link>
      <guid>https://dev.to/clientnshield/clientn-shield-privacy-first-authentication-for-the-modern-web-2meh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update, October 2026:&lt;/strong&gt; ClientN is now live. This early post has been updated.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Your Identity Shouldn't Be the Price of Admission
&lt;/h2&gt;

&lt;p&gt;Every time you sign up for a service, you hand over your email, name, and personal data. ClientN Shield changes that.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/d_j2RQNoDMU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What is ClientN Shield?
&lt;/h2&gt;

&lt;p&gt;ClientN is a privacy-first sign-in service, live since September 2026. It lets people sign in to websites that support it with a passkey, without sharing personal information: each site gets a different anonymous ID.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero personal data shared with websites&lt;/li&gt;
&lt;li&gt;Anonymous authentication that actually works&lt;/li&gt;
&lt;li&gt;Your identity stays yours&lt;/li&gt;
&lt;li&gt;Simple integration for developers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try Our Privacy IQ Quiz
&lt;/h2&gt;

&lt;p&gt;Think you know how your data is being used? Take our &lt;a href="https://clientn.com/quiz/" rel="noopener noreferrer"&gt;Privacy IQ Quiz&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;ClientN is live. &lt;a href="https://clientn.com/signup" rel="noopener noreferrer"&gt;Create a free account&lt;/a&gt;, or see &lt;a href="https://clientn.com/security" rel="noopener noreferrer"&gt;how sign-in is kept safe&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What privacy challenges do you face as a developer? Let me know in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>security</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
