New Pass-ta-key attack reveals all the things we didn't know about passkeys
Why passkey apps treat Windows differently than other operating systems.
找到 58 篇相关文章
Why passkey apps treat Windows differently than other operating systems.
You're debugging a broken SSO login. The identity provider (IdP) redirects back to your app, and somewhere in the request is a big blob called SAMLResponse . You grab it, Base64-decode it, and expect to see clean XML. Sometimes you do. Sometimes you get binary garbage that starts with bytes like 0x78 0x9c and looks nothing like markup. Both outcomes are correct. The difference is which SAML binding the IdP used, and once you know the two encoding chains, SAML debugging stops being guesswork. The two bindings, and their two encodings SAML sends its messages ( SAMLResponse , SAMLRequest ) using one of two HTTP bindings, and they encode the payload differently: HTTP-POST binding — the message rides in a hidden form field that auto-submits via POST. The value is simply: Base64(XML) Decode the Base64 and you get the assertion XML directly. This is the common case for the response coming back from the IdP. HTTP-Redirect binding — the message rides in a URL query string, so it has to be small and URL-safe. The value is: URLEncode( Base64( DEFLATE( XML ) ) ) That's three layers. If you only Base64-decode it, you're staring at the raw output of a DEFLATE compressor — which is exactly the binary garbage people report. This binding is typically used for SAMLRequest (the AuthnRequest your app sends to the IdP) and for Single Logout. Critically, the redirect binding uses raw DEFLATE (RFC 1951) with no zlib header and no checksum . That's the single most common thing people get wrong — they reach for a normal zlib/gzip inflate, it chokes on the missing header, and they conclude the blob is corrupt. It isn't; it just needs a raw inflate. Decoding both in Python import base64 import zlib from urllib.parse import unquote # --- HTTP-POST binding: Base64(XML) --- def decode_post ( saml_response : str ) -> str : return base64 . b64decode ( saml_response ). decode ( " utf-8 " ) # --- HTTP-Redirect binding: URLEncode(Base64(DEFLATE(XML))) --- def decode_redirect ( saml_param : str ) -> s
When Anthropic introduced the Model Context Protocol on November 25, 2024, it got everyone's eyes on it, including Christy, who was Appwrite's Engineering Lead back then. I had just started my role as an "Engineering Intern" and had no idea what a whole new protocol meant, or why it was such a big deal. Looking at the surface, I wasn't entirely wrong. MCP is JSON-RPC with a schema and a handshake stapled on. What took us sixteen months was everything stapled around it. Streamable HTTP did not exist when MCP launched. It replaced HTTP+SSE in the 2025-03-26 revision. The stdio years Christy had a working stdio server in the repo by February 26, 2025. We already had API keys, so the wiring was simple: claude mcp add appwrite \ --env APPWRITE_PROJECT_ID = <YOUR_PROJECT_ID> \ --env APPWRITE_API_KEY = <YOUR_API_KEY> \ --env APPWRITE_ENDPOINT = https://cloud.appwrite.io/v1 \ -- uvx mcp-server-appwrite An API key is scoped to exactly one project by design, so the ceiling was baked into the credential. Switching projects meant editing your editor config. Creating a project was impossible. So was anything at the organization level. The credential is the whole difference between the two transports, and everything hard about the hosted version follows from swapping it for a token that belongs to the user instead of the project. Authorization ate the schedule By the spec, authorization is genuinely optional: Authorization is OPTIONAL for MCP implementations. [...] Implementations using an HTTP-based transport SHOULD conform to this specification. For a service where one tool call can drop a database, we weren't comfortable treating it as optional. If you use Auth0 or WorkOS, this is a config screen. Appwrite keeps everything in-house, so Matej built the authorization server itself, and I built the resource server plus whatever Cloud was still missing before real clients would work. Steps 2 through 6 are the part that makes "just paste this URL" work. Nothing is pre-provisioned.
A hands-on guide to being your own Identity Provider — with Laravel 12 and Passport v13. You will build a Central Portal that acts as an OAuth 2.0 Authorization Server, then wire up child sites ( Site A , Site B , Site C ) so a user logs in once and gets access to all of them. No Google. No Auth0. No Keycloak. No "Sign in with…" anything. You own the users table, you issue the codes and tokens, you hold the signing keys. The only dependency is laravel/passport , which implements the OAuth 2.0 protocol machinery — every identity decision is yours to make, and this guide walks through each one. The scope is deliberately narrow: authentication only. How a user proves who they are at a central server, and how a child site learns that identity. Everything else (admin CRUD screens, audit logging, UI theming) is left out. Everything here is buildable on a fresh Laravel install. No prior context needed. Table of Contents What We Are Building OAuth 2.0 Foundations System Architecture Authentication Workflows Part A — Building the Central Portal Part B — Building a Child Site Registering a Child Site End-to-End Testing Gotchas & Security Notes Reference Tables Appendix A — Extending to Multiple User Types Appendix B — Mental Model in One Page 1. What We Are Building The Problem You operate several web applications. Each has its own users table, its own login form, its own password reset flow. When a staff member joins, someone creates four accounts. When they leave, someone must remember to disable four accounts. Passwords drift out of sync. There is no single place to answer "who has access to what?" The Solution One central server owns identity. Every child site delegates login to it. ┌─────────────────────────────┐ │ Central Portal │ │ ┌───────────────────────┐ │ │ │ admin.portal.test │ │ Management UI │ │ │ │ — create users │ └───────────────────────┘ │ — grant per-site access │ ┌───────────────────────┐ │ │ │ sso.portal.test │ │ OAuth 2.0 endpoints │ │ (authorization ser
This article describes an anonymized enterprise implementation. Company names, internal domains, repository identifiers, ticket numbers, and proprietary control names have been intentionally removed or generalized. Multi-factor authentication is often described as a login problem: enter a password, receive a code, confirm identity. That model was not enough for the system described in this case study. The product ran in an in-store environment where the same tablet could be used by several people during a transaction: an employee initiating the process; a manager approving or supporting it; a customer reviewing and signing on their own device. The challenge was not simply to prove that a user knew a six-digit code. We needed to create a secure, short-lived handoff between a shared in-store session and the customer’s personal phone, without leaking the downstream signing session or allowing multiple devices to claim the same transaction. This post explains the architecture, the security model, the trade-offs, and the production practices behind that platform. The actual problem: secure device handoff The workflow started on a shared tablet. At a certain point, the customer needed to continue part of the process on their own phone. The platform therefore had to answer several questions: How does the phone prove that it belongs to the customer currently standing in front of the employee? How does the shared tablet know that the correct phone claimed the correct session? What happens if the QR code is scanned twice? How do we prevent session identifiers and tokens from appearing in URLs, browser history, logs, or referrer headers? How do we notify the phone immediately when verification succeeds? How do we ensure that a single-use signing URL is never exposed before verification? Those constraints turned a seemingly small MFA feature into a distributed-system problem involving identity, real-time communication, concurrency, edge delivery, infrastructure, and operational
I went looking for a simple answer to a simple question: How do you give an agent access to Google Calendar? Not a demo. Not a screenshot. A real agent, running unattended, with enough access to be useful and enough guardrails that it won’t turn into a security incident. While researching OpenClaw setups, I found a thread on r/openclaw where someone asked what looked like a tiny question: what do I need to add Google Calendar to OpenClaw? One reply said: "Look into gog cli." That answer is way more revealing than it looks. Because the hard part usually isn’t Google Calendar itself. The hard part is everything hidden behind the phrase "connect Google" . And if you’re building agents in n8n, Make, Zapier, OpenClaw, or a custom OpenAI-compatible loop, auth is only half the problem anyway. Once the workflow runs 24/7, you also need to think about retries, quota limits, caching, and how many LLM calls the thing is quietly making in the background. That’s where a lot of teams hit the same wall: the integration works, but the operational shape of it is bad. Security is fuzzy. Request volume is noisy. And AI costs get weird fast if every poll and retry triggers more model calls. The demo version is lying to you If you’ve used something like n8n Cloud, you’ve seen the polished version: Click Google Calendar Sign in Approve access Done That flow is real inside a managed product. But the minute you leave the managed garden — self-hosted n8n, OpenClaw, a custom MCP server, a Python worker on Ubuntu, or your own app using the OpenAI SDK against an OpenAI-compatible endpoint — you inherit the boring parts. Now "connect Google" actually means: create a Google Cloud project configure the OAuth consent screen choose the right OAuth client type enable the Google Calendar API pick the right scopes store credentials safely handle refresh tokens deal with quota errors later That’s not setup trivia. That’s infrastructure. One user in that same OpenClaw discussion realized it immediately:
Harrowing story of an identity theft victim. Yes, the person made a mistake—they gave the scammer a two-factor authentication code that allowed the scammer to take over their email address. But the real story here is how, for many of us, the security of most of our accounts hangs on the security of our email accounts.
YouTube has updated its monetization policies to more clearly define the kinds of AI-generated and low-quality videos that can’t earn ad revenue.
Toute équipe qui développe un logiciel B2B se heurte au même carrefour la première fois qu'un client sérieux annonce « il nous faut le SSO ». Deux acronymes, OIDC et SAML, prétendant chacun être la réponse, et un internet rempli de tableaux comparatifs qui vous disent que SAML est « entreprise » et OIDC « moderne », pour vous laisser exactement aussi coincé qu'avant. Voici la version qui vous aide vraiment à livrer. Ce qu'ils sont SAML date de 2005 et c'est du XML. Un fournisseur d'identité signe une assertion (« voici alice@bigco.com , voici ses groupes ») et la transmet à votre application, qui vérifie la signature et la connecte. Il a été conçu pour le navigateur et pour l'identité des collaborateurs, à une époque où « l'entreprise » signifiait un Active Directory sur site et une pile SOAP. Il est verbeux, il est ancien, et il est absolument partout au sein des grandes organisations, ce qui est le seul fait le concernant qui compte pour vous. OIDC date de 2014 et c'est du JSON et des JWT, posés sur OAuth 2.0. Un fournisseur d'identité émet un jeton d'identité que votre application valide. Il a été conçu pour le web moderne : SPA, applications mobiles, API, connexion sociale. Il est plus propre, mieux spécifié pour ce que vous construisez réellement aujourd'hui, et c'est le protocole que parle désormais la plupart des nouveaux projets d'identité. Quand chacun l'emporte La réponse honnête à « lequel dois-je développer » est que vous n'avez presque jamais le choix. Vous développez celui qu'a choisi le service informatique de votre client, et il l'a choisi bien avant d'avoir entendu parler de vous. Un client sous Okta, Entra ID ou Google Workspace peut généralement faire l'un ou l'autre, et OIDC est la voie la plus agréable. Un client sous un ADFS plus ancien, un IdP historique sur site ou une grille d'achat rédigée en 2016 vous remettra un bloc de métadonnées SAML et une invitation à un rendez-vous, et la discussion s'arrête là. Vos propres applications maison, votr
每一个开发 B2B 软件的团队,都会在第一次有正经客户说出"我们需要 SSO"时撞上同一个岔路口。两个缩写,OIDC 和 SAML,都自称是答案,而满网都是对比表格告诉你 SAML 是"企业级"、OIDC 是"现代化",然后把你撂在原地,跟之前一样毫无头绪。这里给你一个真正能帮你交付的版本。 它们是什么 SAML 来自 2005 年,本质是 XML。身份提供方对一份断言签名("这是 alice@bigco.com ,这是她所属的群组"),然后把它发送给你的应用,应用校验签名并让她登录。它是为浏览器和员工身份场景而生的,那个年代的"企业"意味着本地部署的 Active Directory 和一套 SOAP 技术栈。它冗长、它老旧,而且在大型组织内部无处不在——这才是关于它你唯一需要在意的事实。 OIDC 来自 2014 年,本质是 JSON 和 JWT,构建在 OAuth 2.0 之上。身份提供方签发一个 ID 令牌,由你的应用来校验。它是为现代 Web 而生的:SPA、移动应用、API、社交登录。它更简洁,对你今天真正在构建的东西有更完善的规范,也是如今大多数全新身份方案所使用的协议。 各自何时胜出 对于"我应该构建哪一个"这个问题,老实的答案是:你几乎从来没有选择权。你构建的是你客户的 IT 部门选定的那一个,而且他们早在听说你之前就已经选好了。 一个使用 Okta、Entra ID 或 Google Workspace 的客户通常两种都能用,而 OIDC 是更舒服的那条路。 一个用着老版 ADFS、某个遗留的本地部署 IdP,或一份写于 2016 年的采购清单的客户,会扔给你一堆 SAML 元数据和一封日历邀请,讨论到此为止。 你自己的第一方应用——你的仪表盘和你的移动客户端——要的是 OIDC,没有例外。你绝不会为了让用户登录进你自己的 React 应用而去搬出 SAML。 于是局面清晰地一分为二:现代场景和第一方场景用 OIDC,"因为企业方这么要求"的场景用 SAML。卖给足够多的企业,你就会被要求两者都支持。不是迟早,而是反反复复。 那些坑——也正是自己动手会变得昂贵的地方 SAML 的问题在于它是一种签名 XML 协议,而签名 XML 是应用密码学中最稳定可靠地危险的东西之一。把 SAML 签名校验做错的方式既多又出名: 签名包装(XSW): 攻击者移动已签名的元素,把一份未签名、伪造的断言塞到你的解析器实际读取的位置。如果你把校验签名和读取断言做成两个分开的步骤,那你大概率就有漏洞——而几乎每一个初版实现做的恰恰就是这件事。 规范化与注释注入: 2018 年那一类漏洞, user@company.com<!---->.evil.com 在签名校验时按一种方式规范化、在你代码读取的字符串里按另一种方式规范化,于是你乐呵呵地把错误的人认证通过了。真实存在的 CVE,涉及多个主流库。 那些更不起眼的: 只签名响应却不签名断言、接受未签名的断言、信任 IdP 提供的颁发者却不做固定校验、把断言的有效期窗口算错。每一个都是自己的一颗地雷,而且每一个都被本该懂行的人发布到了生产环境。 OIDC 明显更理智一些,但也并非没有锋利的边角。你仍然得校验正确的声明( iss 、 aud 、 exp 、以及 nonce )、使用 PKCE、拒绝早已作古的 implicit 流程,还要在轮换和缓存 JWKS 时不至于拒掉一个由你尚未拉取的密钥所签名的令牌。区别在于,OIDC 的陷阱有文档可查、是 JSON 形态的,并且在大多数库里默认就被正确处理。SAML 的陷阱是 XML 形态的,已经吞掉过资源远比你充裕的安全团队。 真正的答案 "OIDC 还是 SAML"是个错误的问题,因为对一款 B2B 产品来说,正确的答案是"都要"。你的现代客户和你自己的应用想要 OIDC。你的企业客户会在一个你无法掌控的时间表上强制要求 SAML。只为其中一个去构建,第三通销售电话就会把它打破。 你真正需要的,是一种能接住每个客户带来的任意协议的办法,而不必搭起两套技术栈、两套元数据管线,以及两次各自独立、各自把签名校验做错的机会。实现才是成本所在。选择从来都不是难的那部分。 而这正是 Authagonal 替你卸下的那部分。每个租户都能获得带一键元数据导入的 SAML 2.0,以及与你客户已在使用的提供方对接的 OIDC 联合登录,二者共用同一个登录入口,且任何一种都不收取按连接计费的费用。你不必实现 XML 签名校验,不必照看 JWKS 缓存,也不必在下一个说着另一种协议的客户出现时把这一切重建一遍。 看看都包含了什么。
Originally published at daniel-yang.com . I've been using passkeys for a while now, and at some point I noticed an extension in the WebAuthn spec that almost nobody talks about: PRF. It lets a website ask your authenticator to evaluate a pseudo-random function during login. Deterministic output, 32 bytes, keyed to that specific credential, never leaves your browser. That's an encryption key. Sitting inside the same ceremony everyone already uses for login. So I built pknotes to see how far the idea goes: an end-to-end encrypted notes app with no master password anywhere. Your passkey unlocks your notes in the literal, cryptographic sense. This post is the architecture writeup. There's a live demo if you'd rather poke it first (notes wiped daily). One ceremony, two jobs A normal passkey login proves who you are and nothing else. With the PRF extension, the same ceremony does double duty: The server verifies the WebAuthn assertion. That's login. The client reads the PRF output from the same response and derives a key from it. That's decryption. The server never sees the PRF bytes. They're returned to client-side JavaScript only, after user verification (Face ID, Touch ID, PIN), and only for the requesting origin. Requesting it looks like this: const credential = await navigator . credentials . get ({ publicKey : { challenge , userVerification : ' required ' , extensions : { prf : { eval : { first : new TextEncoder (). encode ( ' pknotes/prf-eval/v1 ' ) } }, }, }, }); const prfOutput = credential . getClientExtensionResults (). prf . results . first ; // 32 bytes, deterministic for this credential + this input, never sent anywhere The key hierarchy Raw PRF output shouldn't encrypt data directly, and you also want to be able to add and remove devices without re-encrypting everything. So there's a small hierarchy: Passkey PRF output │ HKDF-SHA256 ▼ KEK (key-encryption key, exists only in browser memory) │ unwraps ▼ Master key (random AES-256, generated once at signup) │
In today's mobile-first world, users expect authentication to be both secure and effortless. Typing passwords every time an app is opened not only impacts the user experience but also introduces security risks if passwords are weak or reused. Biometric authentication solves this problem by allowing users to verify their identity using Fingerprint , Face ID , Touch ID , Iris Scanner , or even their device's PIN/Password . If you're building a React Native application, @sbaiahmed1/react-native-biometrics is one of the most comprehensive biometric libraries available. Beyond simple authentication prompts, it offers hardware-backed cryptographic key management, biometric enrollment detection, device integrity checks, StrongBox support, and compatibility with both the React Native New Architecture and Expo. In this article, we'll explore everything this library offers and learn how to integrate biometric authentication into a React Native application. Why Biometric Authentication? Traditional authentication methods come with several drawbacks: Passwords are easy to forget. Weak passwords are vulnerable to attacks. OTP-based logins can be slow and frustrating. Users often abandon apps with poor login experiences. Biometric authentication addresses these challenges by providing: 🔒 Enhanced security ⚡ Faster authentication 😊 Better user experience 📱 Native platform support 🔑 Secure fallback using device credentials Whether you're building a banking app, healthcare platform, enterprise application, or e-commerce app, biometric authentication has become an expected feature. Installation Install the package using npm: npm install @ sbaiahmed1 /react-native-biometric s or with Yarn: yarn add @ sbaiahmed1 /react-native-biometric s For iOS: cd ios pod install Platform Configuration Before using biometric authentication, configure the required permissions for both Android and iOS. Android Open your android/app/src/main/AndroidManifest.xml file and add the following permissions: <
When a series of regional outages forced a rethink of a multi-region AWS API, the team discovered that an obstacle to global failover was hiding in plain sight: a pre-flight discovery call baked into every client session years earlier as the only available option. This article describes what it took to remove it, and what the rollout actually cost. By Suresh Gururajan
Auth0’s direction is clear: new extensibility work should be built with Actions, not Rules. Auth0’s docs recommend migrating existing logic step by step, converting pieces of Rule code into Action code, testing in staging, and then rolling out one piece at a time. The platform also highlights that Actions give you modern JavaScript, inline documentation, richer type information, and access to public npm packages. I recently looked at the migration path with one question in mind: how do you move from “old but working” to “clean, testable, future-proof” without breaking login flows? This post is the practical version of that answer. Why Auth0 moved from Rules to Actions Rules were Auth0’s earlier customization layer for authentication flows. Actions are the next-generation extensibility platform, built to replace that model with a more structured developer experience. Auth0 positions Actions as a unified environment with version control, debugging, caching, Node 18 support, and access to millions of npm packages. The biggest shift is not just syntactic. Actions use a modern, promise-based programming model and are organized around triggers such as Post Login. That means you are no longer writing the same kind of callback-style Rule you may have used before; you are moving into a more explicit and modular workflow. The mental model change A Rule usually looks like this: it receives user , context , and callback it runs in a broader authentication pipeline it often mixes business logic with token customization, user metadata updates, and side effects An Action, by contrast, is built around a trigger such as onExecutePostLogin , and it receives an event object plus an api object. Auth0’s migration guide explicitly recommends converting Rule code into Action code in stages rather than copying everything at once. That one change matters because it forces you to separate concerns: what is read from the event what is changed through the API what should happen in this trigger
Cloudflare has recently introduced temporary accounts that let AI agents deploy Cloudflare Workers immediately, without first creating or authenticating with a permanent account. If left unclaimed, the accounts and their deployments expire automatically after 60 minutes. By Renato Losio
The Model Context Protocol team has promoted its Enterprise-Managed Authorisation extension to stable status, adding a centralised way for organisations to control access to MCP servers through their identity provider. The project states the aim is to replace per-server consent prompts with a zero-touch flow in which users sign in once and then access approved servers without further setup. By Matt Saunders
OAuth recovery emails look harmless until you test them the lazy way. A team sends password reset links or recovery codes into one shared mailbox, confirms that something arrived, and marks the job done. From a security view, that test is too weak. It can hide token reuse, wrong-user delivery, or log retention that exposes sensitive account events. For non-production checks, I like using a disposable email address that belongs to one test run. Some teams build that inbox layer themselves, some use tempmailso, but the core principle is the same: isolate the recovery event, inspect it quickly, and delete the evidence you no longer need. That is helpful when Authentication and OAuth changes ship together. Why OAuth recovery emails deserve their own threat model Recovery email tests are not just "did the mail send?" checks. They sit on the edge of account takeover risk, so the message itself matters almost as much as the login flow. A decent threat model for these emails should ask: did the message reach only the intended inbox for this run? does the link or code expire when the product says it does? is the message revealing too much user data in subject lines or previews? can an older token still be used after a new recovery request? do logs or test fixtures keep the recovery secret longer than they should? This is where shared inboxes become dangerous in a subtle way. Even if nobody has bad intent, mixed test data makes it harder to prove which token belonged to which request. The same operational confusion shows up in email change confirmation checks , and it gets worse when the email can restore account access. OWASP recommends testing authentication recovery features with the same care as sign-in and session controls, because weak recovery paths are a common bypass route for stronger primary login defenses: https://cheatsheetseries.owasp.org/cheatsheets/Forgot_Password_Cheat_Sheet.html A safer test flow for recovery links and codes The cleanest pattern is one inbox
Table of contents What is OAUTH A trip to OAUTH1.0Ville What is OAUTH2.0 Examples of OAUTH Technology OIDC Hands-on Implementation with Microsoft Entra ID What is OAUTH OAUTH is a technological standard that allows you to authorize one app or service to sign in to another without divulging private information, such as passwords. OAUTH stands for Open-Authorization , not Authentication . Authentication is a process that verifies your identity, although OAUTH involves identity verification, its main purpose is to grant access to connect you with different apps and services without requiring you to create a new account. How Does OAUTH Work OAUTH uses access tokens, and this is what makes OAUTH secure to use. An access token is a piece of data that contains information about the user and the resource the token is intended for. A token will also include specific rules for data sharing . For example, you want to share your photos from Instagram with Kyrier — An intelligent email platform built for professionals who refuse to let their inbox run their day , but you only want Kyrier to access your profile image. Kyrier does not also need to access your direct messages or friends list. Instagram issues an access token to Kyrier to access the data you approve (your profile image in this case) on your behalf. So an access token will only allow Kyrier to access your profile image, not even other photos on your page. There may be rules governing when Kyrier can use the access token, it might be for a single use or for recurring uses, and it always has an expiration date. A trip to OAUTH1.0Ville Welcome to OAuth1.0Ville. Please keep your hands inside the vehicle. This is where OAuth started. It was built only for websites , back when "an app" meant a web page and nothing else. Although it worked, it had a lot of problems: Only three authorization flows (2.0 has six) No real plan for mobile or modern apps A scaling problem it never solved It also makes you cryptographically sign e
Security is not a feature — it is a property of your entire architecture. This guide covers the security practices implemented in production SaaS applications like tanstackship.com : authentication with password hashing and session management, role-based and attribute-based authorization, data encryption at rest and in transit, API security with CSRF and rate limiting, and ongoing monitoring for vulnerabilities. Authentication: The Identity Layer Session vs Token-Based Auth Aspect Session Auth JWT Auth Hybrid (Recommended) Storage Server-side (D1/Redis) Client-side (localStorage) Server + client Expiry Server-managed Self-contained Dual expiry Revocation Immediate Difficult (until expiry) Session invalidation + JWT refresh Scale Database lookups per request Stateless Cached sessions XSS risk Lower (HTTP-only cookie) Higher (JS-accessible) HTTP-only cookie for session Implementation with Better Auth // src/lib/auth.ts — using Better Auth with Drizzle import { betterAuth } from " better-auth " import { drizzleAdapter } from " better-auth/adapters/drizzle " import { createDb } from " ../db " export const auth = betterAuth ({ database : drizzleAdapter ( createDb ( env ), { provider : " sqlite " , }), emailAndPassword : { enabled : true , autoSignIn : true , passwordHash : { algorithm : " argon2 " , // Argon2id — OWASP recommended params : { memoryCost : 19456 , timeCost : 2 , parallelism : 1 , }, }, }, socialProviders : { google : { clientId : env . GOOGLE_CLIENT_ID , clientSecret : env . GOOGLE_CLIENT_SECRET }, github : { clientId : env . GITHUB_CLIENT_ID , clientSecret : env . GITHUB_CLIENT_SECRET }, }, session : { expiresIn : 7 * 24 * 60 * 60 , // 7 days updateAge : 24 * 60 * 60 , // Refresh every 24 hours }, }) Password Security Checklist [ ] Passwords hashed with Argon2id (not bcrypt, not scrypt) [ ] Minimum 8 characters, no arbitrary complexity rules [ ] Rate-limited login attempts (5 per minute per IP) [ ] Email verification required before first login [ ] Sessio
A JWT isn't just JSON you can inspect. It's a live bearer token. Here's a safer way to decode one. A few days ago I was reviewing a bug with a teammate. They wanted to see what was inside an access token, so they copied it into the first JWT decoder Google returned. It wasn't a dummy token. It was a production access token with almost an hour left before it expired. Nobody was trying to do anything risky—it was just the quickest way to inspect a JWT. That's exactly why this keeps happening. The thing people forget A JWT looks like this: header.payload.signature The payload isn't encrypted. It's just Base64URL-encoded JSON. Because of that, people often think: "The payload isn't secret, so the token is probably safe to paste." Those aren't the same thing. The payload may be readable, but the token itself is still your credential . Anyone holding it can usually authenticate as you until it expires. Why online decoders make me nervous Some JWT tools only decode locally in your browser. Others offer things like signature verification, claim validation, or key management. Features like those often require talking to a backend, which means the token gets sent somewhere else. Maybe the site is trustworthy. Maybe it isn't. From the UI alone, you usually can't tell. Even if a decoder claims everything runs client-side, I don't like assuming that's true when I'm holding a production credential. You don't need a website to inspect a JWT Most of the time I'm only interested in the payload anyway. echo " $TOKEN " \ | cut -d '.' -f2 \ | base64 --decode \ | jq Because JWTs use Base64URL encoding, you may need to translate the alphabet and add padding first: decode_jwt () { local payload = $( echo -n " $1 " | cut -d . -f2 | tr '_-' '/+' ) while [ $(( ${# payload } % 4 )) -ne 0 ] ; do payload = " ${ payload } =" done echo " $payload " | base64 --decode | jq } decode_jwt " $TOKEN " That gives you the claims, expiration time, issuer, audience—everything most people open a decoder for.