<?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: authagonal</title>
    <description>The latest articles on DEV Community by authagonal (@authagonal).</description>
    <link>https://dev.to/authagonal</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%2F3992598%2F3d9e4aed-7459-4f38-80bb-85cef9969f27.png</url>
      <title>DEV Community: authagonal</title>
      <link>https://dev.to/authagonal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/authagonal"/>
    <language>en</language>
    <item>
      <title>Ejecutamos todo un sistema de autenticación sobre almacenes que solo saben hacer get y put</title>
      <dc:creator>authagonal</dc:creator>
      <pubDate>Fri, 10 Jul 2026 11:36:51 +0000</pubDate>
      <link>https://dev.to/authagonal/ejecutamos-todo-un-sistema-de-autenticacion-sobre-almacenes-que-solo-saben-hacer-get-y-put-140p</link>
      <guid>https://dev.to/authagonal/ejecutamos-todo-un-sistema-de-autenticacion-sobre-almacenes-que-solo-saben-hacer-get-y-put-140p</guid>
      <description>&lt;p&gt;Un sistema de autenticación parece pedir a gritos una base de datos relacional. Usuarios, roles, sesiones, concesiones OAuth, refresh tokens, todo referenciado entre sí. El nuestro no usa ninguna. Corre sobre Azure Table Storage, un almacén que te da una partition key, una row key y casi nada más. Sin joins, sin secondary indexes que sirvan de algo, sin operador de incremento, sin transacciones multifila con la forma a la que estás acostumbrado. Fue una decisión. Esto es lo que compra esa decisión, los patrones que la hacen funcionar y la parte en la que SQL habría sido realmente más fácil.&lt;/p&gt;

&lt;p&gt;La razón para hacerlo es el coste y la simplicidad operativa. Sin connection pool que agotar, sin instancia de base de datos que dimensionar, parchear o conmutar por error, con escalado por partición que obtienes gratis. Mantener una tabla no cuesta casi nada y no hay nada que se pueda agotar. El precio de eso es un almacén sin query planner, que solo rinde cuando tus patrones de acceso son tan predecibles como tonto es el almacén. Los de la autenticación lo son, así que todo el diseño se apoya en ello.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La clave hace el trabajo que haría un diseño de tabla.&lt;/strong&gt; Sin joins, diseñas la clave para que la consulta sea la clave. Desnormalizas a propósito, eliges las particiones para que tus lecturas calientes sean point-gets, y codificas identidades compuestas directamente en la row key, por ejemplo &lt;code&gt;"{pk}|{rk}"&lt;/code&gt; para entradas que necesitan una identidad compuesta en un almacén que ofrece un solo hueco de row key. La pregunta "cómo busco esto" hay que responderla al diseñar la clave, no en tiempo de consulta. Para la autenticación eso está bien, porque los patrones de acceso son conocidos y no cambian con el tiempo: obtener un usuario por id, obtener las concesiones de un usuario, consumir un código. No estás explorando los datos. Conoces cada pregunta de antemano.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Llevas tu propio change log.&lt;/strong&gt; Cada fila lleva un &lt;code&gt;Timestamp&lt;/code&gt; gestionado por el servidor, y es tentador construir el backup incremental sobre él: traer todo donde &lt;code&gt;Timestamp gt watermark&lt;/code&gt;. Eso funciona en una demo y muere en producción, porque el almacén indexa la partition key y la row key y nada más. &lt;code&gt;Timestamp&lt;/code&gt; no está en ningún índice, así que filtrar por él inspecciona cada fila de la tabla. Es un full scan disfrazado de consulta, y se vuelve más lento cada día que la tabla crece. Así que, en su lugar, el almacén escribe su propio change log. Cada mutación, cada upsert y cada borrado, agrega una fila a una tabla de change log indexada por el nombre lógico de la tabla, con el compuesto &lt;code&gt;"{pk}|{rk}"&lt;/code&gt; como row key y un flag que indica si la fila se escribió o se borró. Ahora "qué cambió desde el watermark" es la lectura de una única partición pequeña e indexada en lugar de un escaneo de toda la tabla, y el backup hace point-read solo de las filas que de verdad se movieron. Reconstruyes change-data-capture a partir de escrituras ordinarias, y escala con el ritmo de cambios en lugar de con el tamaño de la tabla.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrencia sin transacciones.&lt;/strong&gt; Aquí no hay &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt;. Lo que obtienes en su lugar es una única primitiva atómica: la escritura condicional, que se te entrega como un ETag. Escribes una fila solo si la copia que leíste no ha cambiado por debajo de ti. Todo lo que necesita seguridad bajo acceso concurrente se expresa como optimistic concurrency más reintento en caso de conflicto. El ejemplo más claro es el contador de bloqueo de cuenta. &lt;code&gt;AccessFailedCount&lt;/code&gt; necesita incrementarse de forma atómica, pero el almacén no tiene incremento. Así que lees la fila, subes el contador, la vuelves a escribir condicionada al ETag que leíste, y reintentas si alguien se te adelantó. Ese bucle es cómo haces atómico un contador en un almacén que no tiene contador. Lanza cincuenta contraseñas erróneas a la vez y cada una de ellas surte efecto, porque el conflicto se detecta y se reintenta en lugar de perderse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Un borrado puede ser un bloqueo.&lt;/strong&gt; El consumo de un solo uso, un código de autorización o un token de un solo uso que debe canjearse exactamente una vez, es un borrado condicional. Es un borrado condicional por ETag: si el borrado tiene éxito, ganaste la carrera y puedes continuar; si falla porque la fila ya no estaba, alguien lo consumió antes y te detienes. El borrado es el bloqueo. La misma idea sirve para la leader election, construida sobre un blob lease, un bloqueo hecho de una escritura atómica en lugar de un servicio de coordinación aparte. Casi nunca necesitas un servicio de bloqueos cuando la propia escritura es atómica.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Los borrados tienen que ser de primera clase&lt;/strong&gt;, que es la mitad de la razón por la que existe el change log. Un almacén clave-valor no tiene marcador de borrado: una fila borrada simplemente desaparece, así que cualquier cosa que escaneara en busca de cambios ni siquiera podría ver que se fue. Un backup que se basara en los timestamps de las filas arrastraría en silencio al usuario borrado para siempre. El change log registra el borrado como un borrado, de modo que una restauración lo reproduce en lugar de resucitarlo. Ese modo de fallo, un backup que trae de vuelta fielmente a todos los que eliminaste, es toda una historia en sí misma y merece su propia entrada, pero el patrón pertenece a esta lista: en un almacén sin registro de borrados, el registro de borrados lo llevas tú.&lt;/p&gt;

&lt;p&gt;Ahora el otro lado del balance, aquello a lo que renuncias. Renuncias a las consultas ad-hoc: un patrón de acceso genuinamente nuevo puede significar un nuevo diseño de clave o un full scan, porque no hay query planner que te salve. Renuncias a los joins, así que mantienes copias desnormalizadas a mano y te haces cargo de su consistencia. Renuncias a las transacciones multifila, así que diseñas cada invariante para que viva dentro de un único ítem, porque la atomicidad de un solo ítem es todo lo que se te da. Si tus datos son profundamente relacionales, o tus patrones de acceso son desconocidos y todavía cambiantes, esta es la herramienta equivocada y te va a doler.&lt;/p&gt;

&lt;p&gt;Que es exactamente por lo que le viene bien a la autenticación. Una base de datos relacional se gana el sueldo respondiendo preguntas que aún no se te han ocurrido. Un sistema de autenticación no tiene esas preguntas. El conjunto de cosas que preguntas, obtener este usuario, consumir este código, elegir este líder, respaldar lo que cambió, es pequeño, conocido y estable. Renuncia al query planner que nunca ibas a usar y, a cambio, obtienes una capa de almacenamiento que no cuesta casi nada operar y que no tiene nada que conmutar por error. Para la mayoría del software es un mal trato. Para esto, es el correcto.&lt;/p&gt;

&lt;p&gt;Estos patrones son el motor de almacenamiento que hay debajo de &lt;a href="https://authagonal.io" rel="noopener noreferrer"&gt;Authagonal&lt;/a&gt;: un diseño clave-valor que no cuesta casi nada operar, que es una gran parte de cómo ponemos cada funcionalidad en cada plan.&lt;/p&gt;

</description>
      <category>storage</category>
      <category>architecture</category>
      <category>azure</category>
    </item>
    <item>
      <title>We run a whole auth system on stores that only know get and put</title>
      <dc:creator>authagonal</dc:creator>
      <pubDate>Fri, 10 Jul 2026 11:30:36 +0000</pubDate>
      <link>https://dev.to/authagonal/we-run-a-whole-auth-system-on-stores-that-only-know-get-and-put-3bep</link>
      <guid>https://dev.to/authagonal/we-run-a-whole-auth-system-on-stores-that-only-know-get-and-put-3bep</guid>
      <description>&lt;p&gt;An auth system looks like it wants a relational database. Users, roles, sessions, OAuth grants, refresh tokens, all cross-referenced. Ours does not use one. It runs on Azure Table Storage, a store that gives you a partition key, a row key, and almost nothing else. No joins, no meaningful secondary indexes, no increment operator, no multi-row transactions in the shape you are used to. That was a choice. Here is what the choice buys, the patterns that make it work, and the part where SQL would genuinely have been easier.&lt;/p&gt;

&lt;p&gt;The reason to do this is cost and operational simplicity. No connection pool to exhaust, no database instance to size or patch or fail over, per-partition scaling you get for free. A table costs almost nothing to keep and there is nothing to run out of. The price of that is a store with no query planner, which only pays off when your access patterns are as predictable as the store is dumb. Auth's are, so the whole design leans into it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key does the work a table layout would.&lt;/strong&gt; With no joins, you design the key so the query is the key. You denormalize on purpose, you pick partitions so your hot reads are point-gets, and you encode composite identities directly into the row key, for example &lt;code&gt;"{pk}|{rk}"&lt;/code&gt; for entries that need a compound identity in a store that offers only one row-key slot. The question "how do I look this up" has to be answered when you design the key, not at query time. For auth that is fine, because the access patterns are known and they do not drift: get a user by id, get a user's grants, consume a code. You are not exploring the data. You know every question in advance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You keep your own change log.&lt;/strong&gt; Every row carries a server-managed &lt;code&gt;Timestamp&lt;/code&gt;, and it is tempting to build incremental backup on it: pull everything where &lt;code&gt;Timestamp gt watermark&lt;/code&gt;. That works in a demo and dies in production, because the store indexes the partition key and the row key and nothing else. &lt;code&gt;Timestamp&lt;/code&gt; is not in any index, so filtering on it inspects every row in the table. It is a full scan wearing the costume of a query, and it gets slower every single day the table grows. So the store writes its own change log instead. Every mutation, every upsert and every delete, appends a row to a change-log table keyed by the logical table name, with the composite &lt;code&gt;"{pk}|{rk}"&lt;/code&gt; as the row key and a flag for whether the row was written or deleted. Now "what changed since the watermark" is a read of one small, indexed partition instead of a scan of the whole table, and the backup point-reads only the rows that actually moved. You rebuild change-data-capture out of ordinary writes, and it scales with the churn instead of with the size of the table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrency without transactions.&lt;/strong&gt; There is no &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; here. What you get instead is one atomic primitive: the conditional write, handed to you as an ETag. You write a row only if the copy you read has not changed underneath you. Everything that needs safety under concurrent access is expressed as optimistic concurrency plus retry on conflict. The clearest example is the account-lockout counter. &lt;code&gt;AccessFailedCount&lt;/code&gt; needs to increment atomically, but the store has no increment. So you read the row, bump the count, write it back conditional on the ETag you read, and retry if someone beat you to it. That loop is how you make a counter atomic on a store that has no counter. Fire fifty bad passwords at once and every one of them lands, because the conflict is detected and retried rather than lost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A delete can be a lock.&lt;/strong&gt; Single-use consumption, an authorization code or a one-time token that must be redeemed exactly once, is a conditional delete. It is an ETag conditional-delete: if the delete succeeds you won the race and may proceed; if it fails because the row was already gone, someone else consumed it first and you stop. The delete is the lock. The same idea does leader election, built on a blob lease, a lock made out of an atomic write rather than a separate coordination service. You almost never need a lock service when the write itself is atomic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deletes have to be first-class&lt;/strong&gt;, which is half of why the change log exists. A key-value store has no delete marker: a deleted row simply vanishes, so anything that scanned for changes could never even see that it left. A backup that keyed off row timestamps would quietly carry the deleted user forward forever. The change log records the delete as a delete, so a restore replays it instead of resurrecting it. That failure mode, a backup that faithfully brings back everyone you removed, is a whole story on its own and deserves its own post, but the pattern belongs on this list: in a store with no delete log, you keep the delete log yourself.&lt;/p&gt;

&lt;p&gt;Now the other side of the ledger, what you give up. You give up ad-hoc queries: a genuinely new access pattern can mean a new key design or a full scan, because there is no query planner to save you. You give up joins, so you maintain denormalized copies by hand and own the consistency of that. You give up multi-row transactions, so you design every invariant to live inside a single item, because single-item atomicity is all you are given. If your data is deeply relational, or your access patterns are unknown and still moving, this is the wrong tool and it will hurt.&lt;/p&gt;

&lt;p&gt;Which is exactly why it fits auth. A relational database earns its keep by answering questions you have not thought of yet. An auth system does not have those questions. The set of things you ask, get this user, consume this code, elect this leader, back up what changed, is small, known, and stable. Give up the query planner you were never going to use, and in return you get a storage layer that costs almost nothing to operate and has nothing to fail over. For most software that is a bad trade. For this, it is the right one.&lt;/p&gt;

&lt;p&gt;These patterns are the storage engine under &lt;a href="https://authagonal.io" rel="noopener noreferrer"&gt;Authagonal&lt;/a&gt;: a key-value design that costs almost nothing to operate, which is a big part of how we put every feature on every plan.&lt;/p&gt;

</description>
      <category>storage</category>
      <category>architecture</category>
      <category>azure</category>
    </item>
    <item>
      <title>Your password-reset form is a free email cannon</title>
      <dc:creator>authagonal</dc:creator>
      <pubDate>Thu, 09 Jul 2026 06:26:43 +0000</pubDate>
      <link>https://dev.to/authagonal/your-password-reset-form-is-a-free-email-cannon-14ie</link>
      <guid>https://dev.to/authagonal/your-password-reset-form-is-a-free-email-cannon-14ie</guid>
      <description>&lt;p&gt;The password-reset form is probably the least interesting endpoint you own. It is also the only one an attacker can point at anyone. Ours had no per-email rate limit for a while, and the reason that matters is not the reason you would guess.&lt;/p&gt;

&lt;p&gt;The reflex, when you hear "unauthenticated endpoint, no rate limit," is to think about brute force. But there is nothing to brute force on a reset request. You are not guessing a password. You submit an email address and the system sends a link. Hammering it does not get you into an account. So who gets hurt?&lt;/p&gt;

&lt;p&gt;The victim is a third party. Type someone else's address, &lt;code&gt;victim@example.com&lt;/code&gt;, hit send, repeat. Every request sends a real email to a real inbox you do not control. You have conscripted our reset form into a mailbomb aimed at somebody who never signed up for anything. And because the mail comes from us, the damage lands in two places at once. The victim's inbox fills up. And our sending reputation takes the hit, because a flood of unwanted mail from our domain is exactly what mailbox providers watch for. Enough of it and our legitimate mail, verification links, actual password resets, starts landing in spam for every real customer we have. An unauthenticated endpoint, with no credentials and no cost to the attacker, degrades a resource all of our users share.&lt;/p&gt;

&lt;p&gt;So you rate-limit it. Per email address, so one target cannot be flooded. Easy. Except the obvious implementation quietly opens a different hole.&lt;/p&gt;

&lt;p&gt;If you only apply the limit, or only do the work, when the address actually has an account, then the endpoint behaves differently for real accounts than for made-up ones. An attacker who can see that difference, in the response, the status code, or just the timing, now has an oracle for which email addresses are registered with you. Reset forms are a classic account-enumeration leak for exactly this reason: the naive fix for the mailbomb creates the enumeration bug.&lt;/p&gt;

&lt;p&gt;The real fix has to do both things at once, and the two halves are separate. First, rate-limit on the normalized email address regardless of whether an account exists. The limit key is the address itself, lowercased and trimmed so &lt;code&gt;Victim@&lt;/code&gt; and &lt;code&gt;victim@&lt;/code&gt; share one bucket, and the counter is applied before you have looked anything up. That counter has to live in a durable, shared store, not in a single process's memory, or it evaporates on a restart and does nothing across multiple instances. Second, respond identically in every case: the same status, the same "if that address has an account, we have sent a link" message, the same timing shape, whether or not the address is one of yours. You still only actually send mail when there is an account behind it. But nothing an outsider can observe changes based on that secret. The rate limit protects the third party; the constant response protects the fact of who has an account.&lt;/p&gt;

&lt;p&gt;The general lesson reaches past password resets. Any unauthenticated endpoint that causes a real-world side effect, sending an email, sending an SMS, calling a webhook, is not merely part of your attack surface. It is a weapon aimed at whoever is on the receiving end, and the bill for firing it lands on your reputation, not the attacker's. Rate-limit by the thing being acted on, which is the recipient, not the caller. And do it without letting the presence or absence of secret state change what an observer sees.&lt;/p&gt;

&lt;p&gt;The password-reset form feels like plumbing. It is the one piece of plumbing that will happily spray anyone in the world on command. Meter it accordingly.&lt;/p&gt;

&lt;p&gt;Metering an unauthenticated side-effect endpoint without leaking who has an account is fiddlier than it looks. &lt;a href="https://authagonal.io" rel="noopener noreferrer"&gt;Authagonal&lt;/a&gt; ships the reset flow already rate-limited and enumeration-safe, so your sender reputation is never one curl loop away from ruin.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ratelimiting</category>
      <category>email</category>
    </item>
    <item>
      <title>We gave away every feature on the free tier, including the ones rivals reserve for enterprise</title>
      <dc:creator>authagonal</dc:creator>
      <pubDate>Tue, 07 Jul 2026 23:15:54 +0000</pubDate>
      <link>https://dev.to/authagonal/we-gave-away-every-feature-on-the-free-tier-including-the-ones-rivals-reserve-for-enterprise-1nk4</link>
      <guid>https://dev.to/authagonal/we-gave-away-every-feature-on-the-free-tier-including-the-ones-rivals-reserve-for-enterprise-1nk4</guid>
      <description>&lt;p&gt;Our free plan costs nothing, caps out at 250 monthly active users, and ships single sign-on, SAML, SCIM provisioning, multi-factor authentication, role-based access control, audit logs, and your own branding on your own domain. That feature list is deliberate. It is roughly the same list our competitors file under "Enterprise," or hide behind a "Contact sales" button, or bill per connection at a hundred-plus dollars a month.&lt;/p&gt;

&lt;p&gt;The usual assumption is that this is a loss leader. A bait. Give away the good stuff, get your auth wired into someone's product, then squeeze once they are stuck. It is not that. Giving away every feature is the honest position, and it holds up against a spreadsheet. Here is the math, because the math is the entire argument.&lt;/p&gt;

&lt;p&gt;Start with what a feature actually costs to provide. We made this case in &lt;a href="https://authagonal.io/blog/the-feature-tax" rel="noopener noreferrer"&gt;the feature tax&lt;/a&gt;: SSO, MFA, SCIM and the rest are code written once that now runs for everyone on the platform. The bytes are the same bytes whether someone signs in with a password or a corporate identity provider. When a free tenant switches SAML on, our costs do not move. There is no per-feature meter spinning in a datacenter. The marginal cost of one more tenant using one more feature rounds to zero.&lt;/p&gt;

&lt;p&gt;So if features are free to hand out, what is not? Two things, and only two.&lt;/p&gt;

&lt;p&gt;Active users cost money. More monthly active users means more sessions, more tokens signed, more storage, more egress. That tracks real resource use, and we can measure it. Which is why the free tier has a hard number on it: 250 MAU. Not 250-features-minus-the-expensive-ones. 250 people. Cross that line and you move to a paid plan, because at that point you genuinely cost something to serve.&lt;/p&gt;

&lt;p&gt;Support costs money. A human answering a gnarly integration question at 2am is a real, recurring expense. So is the AI that powers our multilingual support desk, which bills us for every message it translates. The free tier is community-only: docs, forums, a thread on GitHub. No support desk, no AI assist. That is the other thing we hold back, because it is the other thing with a bill attached.&lt;/p&gt;

&lt;p&gt;Notice the shape of that. We gave away everything with a fixed cost and metered the two things with a variable cost. That is the whole model. The wall was never features. The wall is active users and support. A hobby project, an internal tool, a small business with 200 users can run production single sign-on, SCIM deprovisioning, MFA and a real audit trail, for free, indefinitely, and it costs us almost nothing to let them.&lt;/p&gt;

&lt;p&gt;Now look at where the rest of the industry puts its wall. In front of the features. SSO lives three tiers up. MFA is an upgrade. Audit-log retention is sliced by plan. That gate is not recovering a cost, because there is no cost to recover. It is there because your security and compliance features are the ones you can least afford to refuse, which makes them the ones with the most leverage to charge for. A tollbooth on a road that was already built and paid for.&lt;/p&gt;

&lt;p&gt;We would rather not run that experiment on the people using us. The ugly part of feature-gating is that it charges hardest for the responsible thing. You need SSO because your biggest prospect's procurement team insists. You need MFA because your own auditor does. Fencing those behind "Enterprise" is a tax on doing security properly, collected at the precise moment you have the least room to say no. Putting them on the free tier is that same principle from the feature tax followed all the way down: if it costs us nothing to provide, you should not pay for the privilege of turning it on.&lt;/p&gt;

&lt;p&gt;The one real consequence is that a free tenant makes us no money, and that is fine. We make money the boring way, when people grow. A team that starts at 40 users on the free plan with full SSO is a team that, at 4,000 users, pays for scale. Not for switches they were always allowed to flip. By then they have had audit logs and SCIM running in production for a year, which is a far better reason to stay than a feature we finally deign to enable for a fee.&lt;/p&gt;

&lt;p&gt;So yes: every feature, on the free tier, including the ones our competitors reserve for enterprise. Not out of generosity. Because features were never the thing that cost money, and pretending they were is exactly the tax we exist to undercut.&lt;/p&gt;

&lt;p&gt;If you want to see where the line actually sits, &lt;a href="https://authagonal.io/pricing" rel="noopener noreferrer"&gt;here is the pricing&lt;/a&gt;. The free tier is the one up top that says $0 and still ships SAML.&lt;/p&gt;

</description>
      <category>pricing</category>
      <category>saas</category>
      <category>sso</category>
      <category>freetier</category>
    </item>
    <item>
      <title>O imposto de SSO, em dólares: quanto SAML custa de verdade na Auth0, WorkOS e Clerk</title>
      <dc:creator>authagonal</dc:creator>
      <pubDate>Mon, 06 Jul 2026 22:59:53 +0000</pubDate>
      <link>https://dev.to/authagonal/o-imposto-de-sso-em-dolares-quanto-saml-custa-de-verdade-na-auth0-workos-e-clerk-395n</link>
      <guid>https://dev.to/authagonal/o-imposto-de-sso-em-dolares-quanto-saml-custa-de-verdade-na-auth0-workos-e-clerk-395n</guid>
      <description>&lt;p&gt;Escrevemos uma vez sobre &lt;a href="https://authagonal.io/blog/the-feature-tax" rel="noopener noreferrer"&gt;o imposto de funcionalidade&lt;/a&gt;: o truque de cobrar de você para acionar um booleano que já está escrito, sobre uma infraestrutura que já está rodando. Aquele texto era o argumento. Este é o recibo.&lt;/p&gt;

&lt;p&gt;Porque "SSO custa a mais" é fácil de concordar com um aceno de cabeça e fácil de esquecer. Um número numa fatura é mais difícil de esquecer. Então vamos colocar a fatura inteira sobre a mesa, para três momentos pelos quais todo produto B2B passa: a &lt;strong&gt;base da plataforma&lt;/strong&gt; (cobrada por usuários ativos mensais, o que todos cobram e que é justo: mais usuários custam de fato mais para atender), além das duas linhas que realmente medem SAML, a &lt;strong&gt;conexão SSO&lt;/strong&gt; e o &lt;strong&gt;provisionamento SCIM&lt;/strong&gt; que vem junto. Três colunas ao vivo, um total geral. Incluímos a base de propósito. O imposto de SSO não é o único lugar onde o dinheiro vaza; só a diferença no próprio preço da plataforma chega a milhares por ano, e omiti-la &lt;em&gt;subestimaria&lt;/em&gt; o que o modelo de imposto de funcionalidade realmente custa a você. Todas as tarifas são os preços publicados por cada fornecedor em junho de 2026.&lt;/p&gt;

&lt;p&gt;SSO nem é o único medidor, só o mais barulhento. Há um mais silencioso: o limite de &lt;strong&gt;clientes OAuth&lt;/strong&gt;, os apps e serviços que você registra do seu lado. Ele nunca morde durante a avaliação; morde no meio da integração, na tarde em que você vai conectar mais uma ferramenta e descobre que acabou a cota, restando ou reutilizar um cliente que não deveria (sua mesa de suporte emitindo tokens para sua API principal não é uma frase que você queira ler para um auditor de SOC 2) ou pular de plano para adicionar uma única linha a uma tabela. Um cliente é uma linha e um segredo; não custa nada ao fornecedor, então não o medimos. Mas SSO é o imposto que vira manchete, então é ele que as tabelas abaixo precificam.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cenário 1: seu primeiro contrato enterprise
&lt;/h2&gt;

&lt;p&gt;~1.000 MAU. Seu primeiro logo de verdade acabou de assinar, com a condição de fazer login pelo próprio provedor de identidade, com os usuários provisionados automaticamente. &lt;strong&gt;Uma conexão SAML, com SCIM:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fornecedor&lt;/th&gt;
&lt;th&gt;Plano /mês&lt;/th&gt;
&lt;th&gt;SSO /mês&lt;/th&gt;
&lt;th&gt;SCIM /mês&lt;/th&gt;
&lt;th&gt;Total /mês&lt;/th&gt;
&lt;th&gt;Total /ano&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authagonal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$29&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$29&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$319&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clerk&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;$0 &lt;em&gt;(a primeira grátis)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;$300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WorkOS&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$125&lt;/td&gt;
&lt;td&gt;$125&lt;/td&gt;
&lt;td&gt;$250&lt;/td&gt;
&lt;td&gt;$3.000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth0&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;incl.&lt;/td&gt;
&lt;td&gt;incl.&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nesse tamanho, três dos quatro ficam perto de zero. A primeira conexão da Clerk é grátis (seu plano é $25). O nível &lt;em&gt;Free&lt;/em&gt; da Auth0 agora inclui uma conexão enterprise, SSO autosserviço e SCIM por $0, até 25.000 MAU. Genuinamente grátis, e mérito por isso (a pegadinha vem mais abaixo). Nós, $29, tudo incluído. Só a WorkOS quebra a fila: já $3.000/ano, tudo isso o imposto de SSO-e-SCIM por conexão. Até aqui o imposto está em grande parte escondido: adicione um segundo cliente enterprise e veja-o vir à tona.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cenário 2: um punhado de clientes enterprise
&lt;/h2&gt;

&lt;p&gt;~10.000 MAU, o SSO agora faz parte do processo de vendas. &lt;strong&gt;Três conexões SAML, com SCIM:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fornecedor&lt;/th&gt;
&lt;th&gt;Plano /mês&lt;/th&gt;
&lt;th&gt;SSO /mês&lt;/th&gt;
&lt;th&gt;SCIM /mês&lt;/th&gt;
&lt;th&gt;Total /mês&lt;/th&gt;
&lt;th&gt;Total /ano&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authagonal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$149&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$149&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1.639&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clerk&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;$150 &lt;em&gt;(1 grátis + 2×$75)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$175&lt;/td&gt;
&lt;td&gt;$2.100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WorkOS&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$375&lt;/td&gt;
&lt;td&gt;$375&lt;/td&gt;
&lt;td&gt;$750&lt;/td&gt;
&lt;td&gt;$9.000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth0&lt;/td&gt;
&lt;td&gt;orçamento&lt;/td&gt;
&lt;td&gt;orçamento&lt;/td&gt;
&lt;td&gt;orçamento&lt;/td&gt;
&lt;td&gt;orçamento&lt;/td&gt;
&lt;td&gt;orçamento&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Agora somos a linha mais barata da tabela, não por cortar funcionalidades, mas porque a base é tudo o que você paga. Cada uma dessas três conexões é um cliente que paga, então o medidor por conexão acelera exatamente quando o SSO começa a fazer o seu trabalho. A linha SCIM separada da WorkOS já o dobrou para $9.000/ano; a nossa ainda é $1.639, SAML e SCIM incluídos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cenário 3: SSO agora é requisito básico
&lt;/h2&gt;

&lt;p&gt;~50.000 MAU. Dez clientes enterprise, &lt;strong&gt;dez conexões SAML, com SCIM.&lt;/strong&gt; Ninguém mais avalia você sem isso:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fornecedor&lt;/th&gt;
&lt;th&gt;Plano /mês&lt;/th&gt;
&lt;th&gt;SSO /mês&lt;/th&gt;
&lt;th&gt;SCIM /mês&lt;/th&gt;
&lt;th&gt;Total /mês&lt;/th&gt;
&lt;th&gt;Total /ano&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authagonal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$339&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$339&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$3.729&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clerk&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;$675 &lt;em&gt;(1 grátis + 9×$75)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$700&lt;/td&gt;
&lt;td&gt;$8.400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WorkOS&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$1.250&lt;/td&gt;
&lt;td&gt;$1.250&lt;/td&gt;
&lt;td&gt;$2.500&lt;/td&gt;
&lt;td&gt;$30.000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth0&lt;/td&gt;
&lt;td&gt;orçamento&lt;/td&gt;
&lt;td&gt;orçamento&lt;/td&gt;
&lt;td&gt;orçamento&lt;/td&gt;
&lt;td&gt;orçamento&lt;/td&gt;
&lt;td&gt;orçamento&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Leia a linha de cima frente à de baixo. A fatura anual &lt;strong&gt;inteira&lt;/strong&gt; da Authagonal (plataforma, SAML ilimitado, SCIM, MFA, auditoria, marca, tudo) é de &lt;strong&gt;$3.729&lt;/strong&gt;. O imposto de SSO-e-SCIM da WorkOS &lt;strong&gt;sozinho&lt;/strong&gt; é de &lt;strong&gt;$30.000&lt;/strong&gt;: mais de oito vezes a nossa fatura inteira, antes de a WorkOS ter cobrado um único centavo pela plataforma em si, e nem precisa, porque o imposto &lt;em&gt;é&lt;/em&gt; a plataforma. Essa diferença de ~$26.000 por ano é exatamente o que ficava sem menção quando mostrávamos só a linha do SSO. Os bytes são idênticos a um login por senha; o código foi escrito uma vez e entregue a todos. Você estaria pagando, todo ano, por vinte caixas de seleção e uma plataforma que poderia ter tido por cerca de um oitavo do preço.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Sobre os números.&lt;/strong&gt; &lt;em&gt;Plano&lt;/em&gt; é o preço da plataforma escalonado por MAU: a única coisa pela qual nós, e a maioria dos fornecedores, cobramos legitimamente mais à medida que você cresce. A &lt;strong&gt;WorkOS&lt;/strong&gt; inclui gestão de usuários grátis até 1M de MAU, então toda a fatura dela é o imposto por conexão. O &lt;strong&gt;Clerk&lt;/strong&gt; Pro é $25/mês com os MAU em grande parte incluídos nesses volumes (possível um excedente modesto a 50k); a primeira conexão SSO é grátis, depois cerca de $75 cada, e o SCIM vem grátis com uma conexão. A &lt;strong&gt;Auth0&lt;/strong&gt; mudou o modelo recentemente o bastante para merecer o próprio parágrafo, logo abaixo. Os valores anuais da Authagonal são o que de fato é cobrado: 11× o preço mensal, porque os planos anuais ganham um mês grátis (então $29/mês são $319/ano, não $348). Os anuais dos concorrentes são mensal × 12; se algum fornecedor der desconto na cobrança anual, não verificamos, então os totais reais deles podem cair um pouco, nem de longe o bastante para fechar a diferença.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Mais uma sobre a Auth0, porque o modelo dela é o mais sutil.&lt;/strong&gt; A Auth0 agora inclui uma conexão enterprise, SSO e SCIM no plano &lt;em&gt;Free&lt;/em&gt;, grátis até 25.000 MAU. Com uma única conexão isso de fato não custa nada, e mérito por isso. Mas os níveis &lt;em&gt;consumer&lt;/em&gt; pagos os retiram de novo; a nota de rodapé manda você &lt;em&gt;"migrar para B2B"&lt;/em&gt; para manter o SSO. E o B2B custa várias vezes o preço consumer pelos mesmos usuários (o Essentials começa perto de $150/mês contra ~$35), então o verdadeiro imposto de SSO da Auth0 não é por conexão, é o &lt;strong&gt;prêmio B2B&lt;/strong&gt;: um múltiplo do preço base pelo privilégio de vender para empresas. Essa base é precificada por controle deslizante de MAU e passa a personalizada acima de ~20k usuários, e é por isso que a Auth0 mostra &lt;code&gt;quote&lt;/code&gt; nas nossas linhas de 10k e 50k em vez de um palpite interpolado.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Mais dois fornecedores medem por um eixo diferente, então não estão nas tabelas: o mesmo imposto, outro chapéu. A **Okta&lt;/em&gt;* licencia o SSO por usuário, ~$2/usuário à la carte, escalando com as pessoas por trás de cada conexão em vez do número de conexões. O &lt;strong&gt;Duende IdentityServer&lt;/strong&gt; vende SAML como um complemento fixo, ~$1.500/ano somados a uma licença de ~$5.750/ano, e aí você mesmo hospeda, aplica patches, escala e constrói a interface de admin, o MFA e a trilha de auditoria. Uma linha de aparência barata, uma fatura real enorme.)*&lt;/p&gt;

&lt;h2&gt;
  
  
  Perguntas frequentes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Por que tenho que pagar a mais por SSO?&lt;/strong&gt;&lt;br&gt;
Na maioria dos fornecedores, você não paga a mais porque o SSO custa mais a eles. Não custa. Você paga a mais porque os recursos de segurança e conformidade são os que você menos consegue recusar, então são os mais lucrativos para colocar atrás de um muro. SAML é um padrão consolidado, de vinte anos; o custo marginal de habilitá-lo para mais um inquilino arredonda para zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O que é o "imposto de SSO"?&lt;/strong&gt;&lt;br&gt;
A prática de cobrar um prêmio (normalmente por conexão, muitas vezes $75–$125/mês cada, ou forçando um upgrade para um nível Enterprise) para ligar o single sign-on. Há uma lista pública de infratores em sso.tax. É um imposto sobre fazer a coisa responsável, cobrado no momento em que você tem o menor poder de barganha para dizer não.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quanto custa o SSO na Auth0, WorkOS e Clerk?&lt;/strong&gt;&lt;br&gt;
Pelas tarifas publicadas em junho de 2026: a WorkOS cobra cerca de $125/mês por conexão SSO (mais outros ~$125 por SCIM); a Clerk dá uma conexão grátis, depois cerca de $75/mês cada. A Auth0 é a exceção: inclui uma conexão enterprise e SCIM grátis até 25k MAU, depois remove o SSO dos planos pagos de autosserviço e encaminha o uso em produção ou multiconexão para uma trilha B2B apenas sob orçamento. Com dez clientes enterprise, WorkOS e Clerk somam cerca de $15k e $8,1k por ano só pelo SAML; a Auth0 não publica um número para isso. Os valores mudam, então confira a página de preços atual de cada fornecedor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Existe um nível gratuito?&lt;/strong&gt;&lt;br&gt;
Sim. Grátis até 250 usuários ativos mensais, com todos os recursos incluídos, e (ao contrário de qualquer outro nível gratuito) conexões SSO/SAML &lt;strong&gt;ilimitadas&lt;/strong&gt;, não só uma. O limite é apenas de escala: passe de 250 MAU e você migra para um plano pago. Sem cartão de crédito, sem SLA, suporte da comunidade.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Preciso fazer upgrade do meu plano para adicionar outro cliente OAuth ou aplicação?&lt;/strong&gt;&lt;br&gt;
Conosco não. Aplicações não são um eixo pago, então você registra uma por app ou serviço sem mudar de nível. Vários fornecedores limitam ou medem clientes (sobretudo os de máquina para máquina), o que é exatamente o que força a escolha entre reutilizar um cliente que não deveria e pagar pelo próximo nível.&lt;/p&gt;




&lt;p&gt;Dá para saber muito sobre uma empresa pelo que ela cobra de você. A maioria dos fornecedores de autenticação cobra por coisas que não lhes custam nada: um flag SAML, um flag SCIM, uma linha para mais um cliente. O nosso se resume a uma única frase: pague pelo tamanho que você atinge, não pelos interruptores que você tem permissão de acionar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://authagonal.io/compare" rel="noopener noreferrer"&gt;Aqui está exatamente quem cobra pelo quê&lt;/a&gt;, fornecedor por fornecedor. O SSO está do nosso lado da mesa, sem custo adicional, e o mesmo vale para o décimo primeiro cliente que você não sabia que ia precisar. &lt;a href="https://authagonal.io/pricing" rel="noopener noreferrer"&gt;Veja o preço para os seus números.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>auth</category>
      <category>pricing</category>
      <category>sso</category>
      <category>ssotax</category>
    </item>
    <item>
      <title>An identity provider told us who you were, and we believed it</title>
      <dc:creator>authagonal</dc:creator>
      <pubDate>Mon, 06 Jul 2026 22:59:23 +0000</pubDate>
      <link>https://dev.to/authagonal/an-identity-provider-told-us-who-you-were-and-we-believed-it-1b9</link>
      <guid>https://dev.to/authagonal/an-identity-provider-told-us-who-you-were-and-we-believed-it-1b9</guid>
      <description>&lt;p&gt;Single sign-on has a quiet premise: when a user arrives from an identity provider, that provider has already vouched for them, so you can skip the password and just let them in. The whole point is to trust the IdP. The bug we're about to describe lived in the gap between "trust the IdP to authenticate its own users" and "trust whatever the IdP tells you about who its users are." Those are not the same sentence, and the difference was an account takeover.&lt;/p&gt;

&lt;p&gt;Here's the setup. A tenant on our platform can configure federated connections: SAML to their corporate IdP, OIDC to another. A user signs in through one of those connections, the IdP posts back an assertion, and our server has to answer one question: which local account is this? Get that mapping wrong and you have either a stranger locked out of their own account or, far worse, a stranger walking into someone else's.&lt;/p&gt;

&lt;h2&gt;
  
  
  The join key was an email, and an email is just a claim
&lt;/h2&gt;

&lt;p&gt;Our code answered "which account is this" the obvious way. The assertion carried an email, so we looked the user up by it: &lt;code&gt;FindByEmailAsync(assertedEmail)&lt;/code&gt;. If a matching account existed, the returning federated user was resolved onto it and signed in. Clean, simple, and exactly how a lot of SSO integrations are written.&lt;/p&gt;

&lt;p&gt;The problem is what that email actually is. It is not a fact the IdP proved. It is a string in an assertion, and the assertion is only as trustworthy as the connection it came through. In a multi-tenant world you do not control every connection. Anyone who can stand up one (their own SAML IdP, their own OIDC provider) can put any string they like in the email field. So a connection you do not trust asserts &lt;code&gt;email = ceo@othercorp.com&lt;/code&gt;, our lookup finds the real CEO's account, and the attacker is signed in as them. No password, no phishing, no flaw in the crypto. The signature on the assertion was perfectly valid. It just vouched for a claim we should never have treated as an identity.&lt;/p&gt;

&lt;p&gt;The unsettling part is that every individual piece worked. The IdP authenticated &lt;em&gt;its own&lt;/em&gt; user correctly. The signature verified. The account existed. The takeover wasn't a failure of any check; it was using the wrong field as the key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why more validation is not the fix
&lt;/h2&gt;

&lt;p&gt;The tempting reaction is to validate the email harder. Require &lt;code&gt;email_verified&lt;/code&gt;. Check the domain. Add a rule. But notice the shape of the trap: the attacker controls the assertion, so any property you read off the assertion is a property the attacker can set. Demanding &lt;code&gt;email_verified = true&lt;/code&gt; from a connection the attacker owns is asking the fox to certify the henhouse. You cannot validate your way out of trusting the wrong source.&lt;/p&gt;

&lt;p&gt;The email is fine as a convenience. It is a terrible primary key, because it is global (the same address can show up across many providers) and forgeable (it is whatever the asserting connection says it is). A join key has to be neither.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix was to change the key, not to add checks
&lt;/h2&gt;

&lt;p&gt;The real fix was to stop joining on email at all and join on the one thing a connection cannot forge for a provider it does not own: the pair &lt;code&gt;(provider, sub)&lt;/code&gt;. The &lt;code&gt;sub&lt;/code&gt; is the subject identifier the provider issues for that user, scoped to that provider. A returning user is resolved by looking up the local identity previously linked to &lt;em&gt;this&lt;/em&gt; connection's &lt;code&gt;(provider, sub)&lt;/code&gt;. An attacker's connection has its own provider id; it can mint any &lt;code&gt;sub&lt;/code&gt; it likes within its own namespace, but it cannot produce the &lt;code&gt;(provider, sub)&lt;/code&gt; of someone else's connection. The namespace is the moat.&lt;/p&gt;

&lt;p&gt;Email then drops to the role it should always have had: a hint for &lt;em&gt;linking&lt;/em&gt;, never for &lt;em&gt;resolving&lt;/em&gt;, and only when it is vouched. We match an asserted email to an existing account only when the email is verified by a source we actually trust for that domain: &lt;code&gt;email_verified&lt;/code&gt; from a connection whose domain is in the tenant's &lt;code&gt;AllowedDomains&lt;/code&gt;. Outside that, a new federated identity gets its own account, not someone else's.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson, factored out
&lt;/h2&gt;

&lt;p&gt;When you federate identity, separate two questions you're tempted to merge. "Did this provider authenticate this user?" is answered by the signature. "Who is this user in my system?" has to be answered by a key the asserting party cannot forge across boundaries, which means a per-provider subject, not a global attribute like email. Treat every field in an assertion as attacker-controlled unless the specific source of that field is one you trust for that specific claim. Email is the field people reach for first because it's human and unique-looking. It is exactly the wrong one.&lt;/p&gt;

&lt;p&gt;We shipped this as part of a pre-launch security pass over our own auth server, before anyone trusted us with their logins. It's the kind of bug that passes every test, verifies every signature, and hands over the keys to whoever asks in the right format. The fix wasn't a better lock. It was realizing we'd been reading the wrong line of the ID.&lt;/p&gt;

&lt;p&gt;Federating identity safely comes down to joining on a key the asserting party cannot forge, and getting it wrong is an account takeover that passes every test. &lt;a href="https://authagonal.io/features" rel="noopener noreferrer"&gt;Authagonal&lt;/a&gt; resolves federated users by their per-provider subject, never the email in the assertion.&lt;/p&gt;

</description>
      <category>auth</category>
      <category>security</category>
      <category>sso</category>
      <category>saml</category>
    </item>
    <item>
      <title>El scale-to-zero no sabe contar hasta uno</title>
      <dc:creator>authagonal</dc:creator>
      <pubDate>Mon, 06 Jul 2026 00:08:27 +0000</pubDate>
      <link>https://dev.to/authagonal/el-scale-to-zero-no-sabe-contar-hasta-uno-3906</link>
      <guid>https://dev.to/authagonal/el-scale-to-zero-no-sabe-contar-hasta-uno-3906</guid>
      <description>&lt;p&gt;El consejo siempre tuvo la misma forma. Tu tráfico de autenticación llega en picos. Tu clúster de Kubernetes está mayormente en silencio. Azure Container Apps escala a cero y te cobra por lo que usas. Estás pagando las 24 horas del día por un clúster que pasa la mayor parte de su vida esperando.&lt;/p&gt;

&lt;p&gt;Lo tomamos en serio hasta el punto de calcular correctamente el costo de la migración. Luego la descartamos. No por el precio de la transición, ni por los arranques en frío, aunque los arranques en frío solos deberían haber sido suficientes. La descartamos porque al vocabulario de la plataforma le falta un número, y es el único número que realmente le importa a nuestro núcleo de autenticación.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless cuenta hasta cero y hasta N
&lt;/h2&gt;

&lt;p&gt;Los runtimes de scale-to-zero conocen dos números. Cero, cuando nadie llama. N, cuando lo hacen, con N flotando según la demanda. Para el manejo de solicitudes sin estado, ese vocabulario es perfecto, razón por la cual el consejo suena tan bien. La mayoría de los backends web realmente tienen esa forma: cada solicitud independiente, cada instancia desechable, cero tráfico mereciendo una factura de cero.&lt;/p&gt;

&lt;p&gt;La cara pública de un sistema de autenticación se ve así también. Puntos finales de tokens, páginas de inicio de sesión, JWKS. Sin estado, con picos, amigable con la caché.&lt;/p&gt;

&lt;p&gt;Debajo hay una capa que no es nada de eso.&lt;/p&gt;

&lt;h2&gt;
  
  
  El número es uno
&lt;/h2&gt;

&lt;p&gt;Bajo los puntos finales, nuestra backplane ejecuta una capa de coordinación. En cualquier momento, exactamente una réplica tiene el arrendamiento de liderazgo del clúster (un arrendamiento de blob hoy en día, ya no gossip, que es una historia para otra publicación) y ejecuta los trabajos singleton: el barrido de retención, el pase de entrega de webhooks, el trabajo donde dos ejecutores concurrentes significan efectos secundarios disparados por duplicado y cero ejecutores significa que las cosas, silenciosamente, no suceden nunca.&lt;/p&gt;

&lt;p&gt;El número que esa capa necesita es uno. No cero cuando todo está tranquilo. No N bajo carga. Uno, mantenido continuamente, con una transferencia verificable cuando el titular muere. El scale-to-zero no tiene forma de decir "uno, siempre". Puede decir "al menos uno mientras haya tráfico", lo cual es una promesa diferente, y la diferencia entre esas dos promesas es exactamente el modo de fallo que una elección de líder existe precisamente para prevenir.&lt;/p&gt;

&lt;p&gt;Por eso el gráfico de utilización nos estaba mintiendo. Un clúster de autenticación tranquilo no está de brazos cruzados. Está manteniendo un arrendamiento, manteniendo las claves de firma calientes y listo para responder a la siguiente validación de token en milisegundos de un solo dígito. Silencioso e inactivo son estados diferentes, y los paneles de facturación solo te muestran uno de ellos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Puedes fingirlo, y eso es peor
&lt;/h2&gt;

&lt;p&gt;Puedes forzar "exactamente uno" en un runtime serverless. Fija las réplicas mínimas en uno y has comprado un servidor siempre activo en una plataforma cuyo instinto completo es quitarte las instancias silenciosas. Cada evento de escalado, cada reinicio iniciado por la plataforma, cada cambio de revisión se convierte en una cuestión de liderazgo cuyo momento no controlas. Estaríamos luchando contra el comportamiento central del runtime para preservar el nuestro, para siempre, y pagando por el privilegio.&lt;/p&gt;

&lt;p&gt;Y el fallo recae sobre la peor persona posible. La solicitud que paga un arranque en frío es un humano tratando de iniciar sesión. "Tu inicio de sesión fue lento porque nuestro servicio de autenticación estaba dormido" no es una frase que nadie debería llegar a publicar.&lt;/p&gt;

&lt;h2&gt;
  
  
  La factura tuvo una solución aburrida
&lt;/h2&gt;

&lt;p&gt;¿Y el dinero con el que empezó todo esto? Bastaron un planificador y una SKU. El clúster de desarrollo ahora se desasigna cuando nadie lo está usando, y el grupo de nodos pasó a una SKU más barata. Un párrafo es todo lo que eso merece, y ese es el punto: "dejar de pagar por inactividad" es un objetivo, no una arquitectura, y la mayor parte del objetivo era alcanzable con un cambio de configuración en lugar de un cambio de plataforma.&lt;/p&gt;

&lt;h2&gt;
  
  
  La prueba de forma
&lt;/h2&gt;

&lt;p&gt;La regla que nos quedamos: hacer coincidir el runtime con la forma real de la carga de trabajo, no con su gráfico de tráfico. Serverless recompensa lo que es sin estado y a ráfagas. Castiga cualquier cosa que deba mantener un rol continuo y exclusivo, y un núcleo de autenticación que protege las claves de firma y elige quién ejecuta los trabajos destructivos es el ejemplo más puro de ese segundo tipo que conocemos.&lt;/p&gt;

&lt;p&gt;No estamos en contra del serverless. Gran parte de nuestra superficie sin estado viviría felizmente en él. Pero la capa que tiene que contar exactamente hasta uno se queda en una infraestructura aburrida, siempre activa e inspeccionable.&lt;/p&gt;

&lt;p&gt;Esa capa es también precisamente lo que dejas de operar cuando ejecutas la autenticación en &lt;a href="https://authagonal.io" rel="noopener noreferrer"&gt;Authagonal&lt;/a&gt; en lugar de ejecutar la backplane tú mismo.&lt;/p&gt;

</description>
      <category>auth</category>
      <category>infrastructure</category>
      <category>kubernetes</category>
      <category>aks</category>
    </item>
    <item>
      <title>Scale-to-zero can't count to one</title>
      <dc:creator>authagonal</dc:creator>
      <pubDate>Mon, 06 Jul 2026 00:04:26 +0000</pubDate>
      <link>https://dev.to/authagonal/scale-to-zero-cant-count-to-one-2c29</link>
      <guid>https://dev.to/authagonal/scale-to-zero-cant-count-to-one-2c29</guid>
      <description>&lt;p&gt;The advice always had the same shape. Your auth traffic is spiky. Your Kubernetes cluster sits mostly quiet. Azure Container Apps scales to zero and bills you for what you use. You are paying around the clock for a cluster that spends most of its life waiting.&lt;/p&gt;

&lt;p&gt;We took it seriously enough to cost the migration properly. Then we killed it. Not because of the price of the cutover, and not because of cold starts, although cold starts alone should have been enough. We killed it because the platform's vocabulary is missing a number, and it's the only number our auth core actually cares about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless counts to zero and to N
&lt;/h2&gt;

&lt;p&gt;Scale-to-zero runtimes know two numbers. Zero, when nobody is calling. N, when they are, with N floating on demand. For stateless request handling that vocabulary is perfect, which is why the advice sounds so right. Most web backends really are that shape: every request independent, every instance disposable, zero traffic deserving a zero bill.&lt;/p&gt;

&lt;p&gt;An auth system's public face looks like that too. Token endpoints, login pages, JWKS. Stateless, spiky, cache-friendly.&lt;/p&gt;

&lt;p&gt;Underneath it is a layer that is none of those things.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number is one
&lt;/h2&gt;

&lt;p&gt;Below the endpoints, our backplane runs a coordination layer. At any moment exactly one replica holds the cluster leadership lease (a blob lease these days, not gossip, which is a story for another post) and runs the singleton jobs: the retention sweep, the webhook delivery pass, the work where two concurrent runners means double-fired side effects and zero runners means things silently never happen.&lt;/p&gt;

&lt;p&gt;The number that layer needs is one. Not zero when things are quiet. Not N under load. One, held continuously, with a verifiable handover when the holder dies. Scale-to-zero has no way to say "one, always". It can say "at least one while there's traffic", which is a different promise, and the difference between those two promises is exactly the failure mode a leader election exists to prevent.&lt;/p&gt;

&lt;p&gt;This is why the utilization graph was lying to us. A quiet auth cluster is not doing nothing. It is holding a lease, keeping signing keys hot, and standing ready to answer the next token validation in single-digit milliseconds. Quiet and idle are different states, and billing dashboards only show you one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  You can fake it, and that's worse
&lt;/h2&gt;

&lt;p&gt;You can force "exactly one" onto a serverless runtime. Pin minimum replicas to one and you have bought an always-on server on a platform whose whole instinct is to take quiet instances away from you. Every scale event, every platform-initiated restart, every revision swap becomes a leadership question you don't control the timing of. We would be fighting the runtime's core behavior to preserve ours, forever, and paying for the privilege.&lt;/p&gt;

&lt;p&gt;And the failure lands on the worst possible person. The request that pays a cold start is a human trying to log in. "Your sign-in was slow because our auth service was asleep" is not a sentence anyone should ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bill had a boring fix
&lt;/h2&gt;

&lt;p&gt;What about the money that started all this? It fell to a scheduler and a SKU. The dev cluster now deallocates when nobody is using it, and the node pool moved to a cheaper SKU. One paragraph is all that deserves, and that's the point: "stop paying for idle" is a goal, not an architecture, and most of the goal was reachable with a config change instead of a re-platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape test
&lt;/h2&gt;

&lt;p&gt;The rule we kept: match the runtime to the workload's actual shape, not to its traffic graph. Serverless rewards stateless and bursty. It punishes anything that must hold a continuous, exclusive role, and an auth core that guards signing keys and elects who runs the destructive jobs is the purest example of that second kind we know.&lt;/p&gt;

&lt;p&gt;We're not against serverless. Plenty of our stateless surface would live happily on it. But the layer that has to count to exactly one is staying on boring, always-on, inspectable infrastructure.&lt;/p&gt;

&lt;p&gt;That layer is also precisely what you stop operating when you run auth on &lt;a href="https://authagonal.io" rel="noopener noreferrer"&gt;Authagonal&lt;/a&gt; instead of running the backplane yourself.&lt;/p&gt;

</description>
      <category>auth</category>
      <category>infrastructure</category>
      <category>kubernetes</category>
      <category>aks</category>
    </item>
    <item>
      <title>El impuesto al SSO, en dólares: lo que SAML cuesta de verdad en Auth0, WorkOS y Clerk</title>
      <dc:creator>authagonal</dc:creator>
      <pubDate>Fri, 03 Jul 2026 01:34:50 +0000</pubDate>
      <link>https://dev.to/authagonal/el-impuesto-al-sso-en-dolares-lo-que-saml-cuesta-de-verdad-en-auth0-workos-y-clerk-12mn</link>
      <guid>https://dev.to/authagonal/el-impuesto-al-sso-en-dolares-lo-que-saml-cuesta-de-verdad-en-auth0-workos-y-clerk-12mn</guid>
      <description>&lt;p&gt;Escribimos una vez sobre &lt;a href="https://authagonal.io/blog/the-feature-tax" rel="noopener noreferrer"&gt;el impuesto a las funciones&lt;/a&gt;: el truco de cobrarte por activar un booleano que ya está escrito, sobre una infraestructura que ya está en marcha. Aquel artículo era el argumento. Este es el recibo.&lt;/p&gt;

&lt;p&gt;Porque «el SSO cuesta extra» es fácil de aceptar con un gesto y fácil de olvidar. Una cifra en una factura cuesta más de olvidar. Así que pongamos toda la factura sobre la mesa, para tres momentos que atraviesa todo producto B2B: la &lt;strong&gt;base de la plataforma&lt;/strong&gt; (facturada por usuarios activos mensuales, lo que todos cobran y es justo: más usuarios cuestan de verdad más de atender), más las dos líneas que realmente miden SAML, la &lt;strong&gt;conexión SSO&lt;/strong&gt; y el &lt;strong&gt;aprovisionamiento SCIM&lt;/strong&gt; que la acompaña. Tres columnas en vivo, un total global. Incluimos la base a propósito. El impuesto al SSO no es el único lugar donde se escapa el dinero; la brecha en el precio de la plataforma misma asciende a miles al año, y omitirla &lt;em&gt;subestimaría&lt;/em&gt; lo que el modelo del impuesto a las funciones te cuesta de verdad. Todas las tarifas corresponden a los precios publicados por cada proveedor en junio de 2026.&lt;/p&gt;

&lt;p&gt;El SSO ni siquiera es el único medidor, solo el más ruidoso. Hay uno más silencioso: el límite de &lt;strong&gt;clientes OAuth&lt;/strong&gt;, las apps y servicios que registras de tu lado. Nunca muerde durante la evaluación; muerde a mitad de la integración, la tarde en que vas a conectar una herramienta más y descubres que te has quedado sin cupo, con la única opción de reutilizar un cliente que no deberías (que tu mesa de soporte emita tokens para tu API principal no es una frase que quieras leerle a un auditor de SOC 2) o saltar de nivel para añadir una sola fila a una tabla. Un cliente es una fila y un secreto; no le cuesta nada al proveedor, así que no lo medimos. Pero el SSO es el impuesto que da titulares, así que es eso lo que calculan las tablas de abajo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Escenario 1: tu primer acuerdo enterprise
&lt;/h2&gt;

&lt;p&gt;~1.000 MAU. Tu primer logo de verdad acaba de firmar, con la condición de iniciar sesión a través de su propio proveedor de identidad, con sus usuarios aprovisionados automáticamente. &lt;strong&gt;Una conexión SAML, con SCIM:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Proveedor&lt;/th&gt;
&lt;th&gt;Plan /mes&lt;/th&gt;
&lt;th&gt;SSO /mes&lt;/th&gt;
&lt;th&gt;SCIM /mes&lt;/th&gt;
&lt;th&gt;Total /mes&lt;/th&gt;
&lt;th&gt;Total /año&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authagonal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$29&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$29&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$319&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clerk&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;$0 &lt;em&gt;(la primera gratis)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;$300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WorkOS&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$125&lt;/td&gt;
&lt;td&gt;$125&lt;/td&gt;
&lt;td&gt;$250&lt;/td&gt;
&lt;td&gt;$3.000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth0&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;incl.&lt;/td&gt;
&lt;td&gt;incl.&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A este tamaño, tres de los cuatro rondan el cero. La primera conexión de Clerk es gratis (su plan cuesta $25). El nivel &lt;em&gt;Free&lt;/em&gt; de Auth0 ahora incluye una conexión enterprise, SSO autoservicio y SCIM por $0, hasta 25.000 MAU. Gratis de verdad, y mérito por ello (la trampa viene más abajo). Nosotros, $29, todo incluido. Solo WorkOS se sale de la fila: $3.000/año ya, todo ello el impuesto al SSO-y-SCIM por conexión. Hasta ahora el impuesto se esconde en buena medida: añade un segundo cliente enterprise y míralo salir a la superficie.&lt;/p&gt;

&lt;h2&gt;
  
  
  Escenario 2: un puñado de clientes enterprise
&lt;/h2&gt;

&lt;p&gt;~10.000 MAU, el SSO ya forma parte del proceso de venta. &lt;strong&gt;Tres conexiones SAML, con SCIM:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Proveedor&lt;/th&gt;
&lt;th&gt;Plan /mes&lt;/th&gt;
&lt;th&gt;SSO /mes&lt;/th&gt;
&lt;th&gt;SCIM /mes&lt;/th&gt;
&lt;th&gt;Total /mes&lt;/th&gt;
&lt;th&gt;Total /año&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authagonal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$149&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$149&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1.639&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clerk&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;$150 &lt;em&gt;(1 gratis + 2×$75)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$175&lt;/td&gt;
&lt;td&gt;$2.100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WorkOS&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$375&lt;/td&gt;
&lt;td&gt;$375&lt;/td&gt;
&lt;td&gt;$750&lt;/td&gt;
&lt;td&gt;$9.000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth0&lt;/td&gt;
&lt;td&gt;presupuesto&lt;/td&gt;
&lt;td&gt;presupuesto&lt;/td&gt;
&lt;td&gt;presupuesto&lt;/td&gt;
&lt;td&gt;presupuesto&lt;/td&gt;
&lt;td&gt;presupuesto&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ahora somos la línea más barata de la tabla, no por recortar funciones, sino porque la base es todo lo que pagas. Cada una de esas tres conexiones es un cliente que paga, así que el medidor por conexión se acelera justo cuando el SSO empieza a hacer su trabajo. La línea SCIM aparte de WorkOS ya lo ha duplicado a $9.000/año; la nuestra sigue en $1.639, SAML y SCIM incluidos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Escenario 3: el SSO ya es requisito básico
&lt;/h2&gt;

&lt;p&gt;~50.000 MAU. Diez clientes enterprise, &lt;strong&gt;diez conexiones SAML, con SCIM.&lt;/strong&gt; Ya nadie te evalúa sin ello:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Proveedor&lt;/th&gt;
&lt;th&gt;Plan /mes&lt;/th&gt;
&lt;th&gt;SSO /mes&lt;/th&gt;
&lt;th&gt;SCIM /mes&lt;/th&gt;
&lt;th&gt;Total /mes&lt;/th&gt;
&lt;th&gt;Total /año&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authagonal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$339&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$339&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$3.729&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clerk&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;$675 &lt;em&gt;(1 gratis + 9×$75)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$700&lt;/td&gt;
&lt;td&gt;$8.400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WorkOS&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$1.250&lt;/td&gt;
&lt;td&gt;$1.250&lt;/td&gt;
&lt;td&gt;$2.500&lt;/td&gt;
&lt;td&gt;$30.000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth0&lt;/td&gt;
&lt;td&gt;presupuesto&lt;/td&gt;
&lt;td&gt;presupuesto&lt;/td&gt;
&lt;td&gt;presupuesto&lt;/td&gt;
&lt;td&gt;presupuesto&lt;/td&gt;
&lt;td&gt;presupuesto&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Lee la fila de arriba frente a la de abajo. La factura anual &lt;strong&gt;entera&lt;/strong&gt; de Authagonal (plataforma, SAML ilimitado, SCIM, MFA, auditoría, marca, todo) es de &lt;strong&gt;$3.729&lt;/strong&gt;. El impuesto al SSO-y-SCIM de WorkOS &lt;strong&gt;por sí solo&lt;/strong&gt; es de &lt;strong&gt;$30.000&lt;/strong&gt;: más de ocho veces nuestra factura entera, antes de que WorkOS haya cobrado un solo centavo por la plataforma en sí, y no le hace falta, porque el impuesto &lt;em&gt;es&lt;/em&gt; la plataforma. Esa brecha de ~$26.000 al año es exactamente lo que quedaba sin mencionar cuando solo mostrábamos la línea del SSO. Los bytes son idénticos a un inicio de sesión con contraseña; el código se escribió una vez y se entregó a todos. Estarías pagando, cada año, por veinte casillas de verificación y una plataforma que podrías haber tenido por aproximadamente la octava parte del precio.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Sobre las cifras.&lt;/strong&gt; &lt;em&gt;Plan&lt;/em&gt; es el precio de la plataforma escalonado por MAU: lo único por lo que nosotros, y la mayoría de proveedores, cobramos legítimamente más a medida que creces. &lt;strong&gt;WorkOS&lt;/strong&gt; incluye la gestión de usuarios gratis hasta 1M de MAU, así que toda su factura es el impuesto por conexión. &lt;strong&gt;Clerk&lt;/strong&gt; Pro cuesta $25/mes con los MAU mayormente incluidos a estos volúmenes (posible un modesto exceso a 50k); su primera conexión SSO es gratis, luego unos $75 cada una, y SCIM viene gratis con una conexión. &lt;strong&gt;Auth0&lt;/strong&gt; cambió su modelo hace poco lo suficiente como para merecer su propio párrafo, justo debajo. Las cifras anuales de Authagonal son lo que de verdad se factura: 11× el precio mensual, porque los planes anuales regalan un mes (así que $29/mes son $319/año, no $348). Los precios anuales de la competencia son mensual × 12; si algún proveedor descuenta la facturación anual no lo hemos verificado, así que sus totales reales podrían bajar un poco, ni de lejos lo suficiente para cerrar la brecha.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Una cosa más sobre Auth0, porque su modelo es el más sutil.&lt;/strong&gt; Auth0 ahora incluye una conexión enterprise, SSO y SCIM en su plan &lt;em&gt;Free&lt;/em&gt;, gratis hasta 25.000 MAU. Con una sola conexión eso no cuesta nada de verdad, y mérito por ello. Pero sus niveles &lt;em&gt;consumer&lt;/em&gt; de pago los vuelven a quitar; la nota al pie te dice que &lt;em&gt;«pases a B2B»&lt;/em&gt; para conservar el SSO. Y B2B cuesta varias veces el precio consumer por los mismos usuarios (Essentials arranca cerca de $150/mes frente a ~$35), así que el verdadero impuesto al SSO de Auth0 no es por conexión, es la &lt;strong&gt;prima B2B&lt;/strong&gt;: un múltiplo del precio base por el privilegio de venderles a empresas. Esa base se tarifica con deslizador por MAU y pasa a personalizada por encima de ~20k usuarios, razón por la cual Auth0 muestra &lt;code&gt;quote&lt;/code&gt; en nuestras filas de 10k y 50k en lugar de una estimación interpolada.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Otros dos proveedores miden sobre un eje distinto, así que no están en las tablas: mismo impuesto, otro sombrero. **Okta&lt;/em&gt;* licencia el SSO por usuario, ~$2/usuario a la carta, escalando con los humanos detrás de cada conexión en lugar del número de conexiones. &lt;strong&gt;Duende IdentityServer&lt;/strong&gt; vende SAML como un complemento plano, ~$1.500/año además de una licencia de ~$5.750/año, y luego tú alojas, parcheas, escalas y construyes tú mismo la interfaz de administración, el MFA y el registro de auditoría. Una línea de aspecto barato, una factura real enorme.)*&lt;/p&gt;

&lt;h2&gt;
  
  
  Preguntas frecuentes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;¿Por qué tengo que pagar extra por el SSO?&lt;/strong&gt;&lt;br&gt;
En la mayoría de proveedores, no pagas extra porque el SSO les cueste más. No es así. Pagas extra porque las funciones de seguridad y cumplimiento son las que menos puedes rechazar, así que son las más rentables de poner tras un muro. SAML es un estándar asentado, de veinte años; el costo marginal de habilitarlo para un inquilino más se redondea a cero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Qué es el «impuesto al SSO»?&lt;/strong&gt;&lt;br&gt;
La práctica de cobrar una prima (normalmente por conexión, a menudo $75–$125/mes cada una, o forzando una subida a un nivel Enterprise) para activar el inicio de sesión único. Hay una lista pública de infractores en sso.tax. Es un impuesto por hacer lo responsable, cobrado en el momento en que menos margen tienes para decir que no.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Cuánto cuesta el SSO en Auth0, WorkOS y Clerk?&lt;/strong&gt;&lt;br&gt;
Según sus tarifas publicadas en junio de 2026: WorkOS cobra unos $125/mes por conexión SSO (más otros ~$125 por SCIM); Clerk te da una conexión gratis, luego unos $75/mes cada una. Auth0 es el que se sale de la norma: incluye una conexión enterprise y SCIM gratis hasta 25k MAU, luego retira el SSO de sus planes de pago de autoservicio y deriva el uso en producción o multiconexión a una vía B2B solo bajo presupuesto. Con diez clientes enterprise, WorkOS y Clerk rondan los $15k y $8,1k al año solo por SAML; Auth0 no publica una cifra para ello. Las cifras cambian, así que consulta la página de precios actual de cada proveedor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Hay un nivel gratuito?&lt;/strong&gt;&lt;br&gt;
Sí. Gratis hasta 250 usuarios activos mensuales, con todas las funciones incluidas, y (a diferencia de cualquier otro nivel gratuito) conexiones SSO/SAML &lt;strong&gt;ilimitadas&lt;/strong&gt;, no solo una. El tope es únicamente de escala: supera los 250 MAU y pasas a un plan de pago. Sin tarjeta de crédito, sin SLA, soporte de la comunidad.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Tengo que subir de plan para añadir otro cliente OAuth o aplicación?&lt;/strong&gt;&lt;br&gt;
Con nosotros no. Las aplicaciones no son un eje de pago, así que registras una por app o servicio sin cambiar de nivel. Varios proveedores sí limitan o miden los clientes (sobre todo los de máquina a máquina), que es lo que fuerza la elección entre reutilizar un cliente que no deberías y pagar por el siguiente nivel.&lt;/p&gt;




&lt;p&gt;Se puede saber mucho de una empresa por aquello que te cobra. La mayoría de los proveedores de autenticación cobran por cosas que a ellos no les cuestan nada: un flag SAML, un flag SCIM, una fila para un cliente más. El nuestro se reduce a una sola línea: paga por lo grande que llegues a ser, no por qué interruptores se te permite activar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://authagonal.io/compare" rel="noopener noreferrer"&gt;Aquí tienes exactamente quién cobra por qué&lt;/a&gt;, proveedor por proveedor. El SSO está en nuestro lado de la mesa, sin recargo, y lo mismo vale para el undécimo cliente que no sabías que ibas a necesitar. &lt;a href="https://authagonal.io/pricing" rel="noopener noreferrer"&gt;Míralo con el precio para tus cifras.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>auth</category>
      <category>pricing</category>
      <category>sso</category>
      <category>ssotax</category>
    </item>
    <item>
      <title>The SSO tax, in dollars: what SAML actually costs at Auth0, WorkOS, and Clerk</title>
      <dc:creator>authagonal</dc:creator>
      <pubDate>Fri, 03 Jul 2026 01:34:10 +0000</pubDate>
      <link>https://dev.to/authagonal/the-sso-tax-in-dollars-what-saml-actually-costs-at-auth0-workos-and-clerk-3437</link>
      <guid>https://dev.to/authagonal/the-sso-tax-in-dollars-what-saml-actually-costs-at-auth0-workos-and-clerk-3437</guid>
      <description>&lt;p&gt;We wrote once about &lt;a href="https://authagonal.io/blog/the-feature-tax" rel="noopener noreferrer"&gt;the feature tax&lt;/a&gt;: the trick of charging you to flip a boolean that's already written, on infrastructure that's already running. That piece was the argument. This one is the receipt.&lt;/p&gt;

&lt;p&gt;Because "SSO costs extra" is easy to nod along to and easy to forget. A number on an invoice is harder to forget. So let's put the whole invoice on the table for three moments every B2B product passes through: the &lt;strong&gt;platform base&lt;/strong&gt; (priced by monthly active users, which everyone charges and which is fair: more users genuinely cost more to serve), plus the two line items that actually meter SAML, the &lt;strong&gt;SSO connection&lt;/strong&gt; and the &lt;strong&gt;SCIM provisioning&lt;/strong&gt; that rides it. Three live columns, one all-in total. We include the base on purpose. The SSO tax isn't the only place money leaks; the gap in the platform price itself runs to thousands a year, and leaving it out would &lt;em&gt;understate&lt;/em&gt; what the feature-tax model really costs you. All rates are each vendor's published June 2026 pricing.&lt;/p&gt;

&lt;p&gt;SSO isn't even the only meter, just the loudest. There's a quieter one: the cap on &lt;strong&gt;OAuth clients&lt;/strong&gt;, the apps and services you register on your own side. It never bites during evaluation; it bites mid-integration, the afternoon you go to wire up one more tool and find you're out, left to either reuse a client you shouldn't (your support desk minting tokens for your core API is not a sentence you want to read to a SOC 2 auditor) or jump a tier to add one row to a table. A client is a row and a secret; it costs the vendor nothing, so we don't meter it. But SSO is the headline tax, so that's what the tables below price.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario 1: your first enterprise deal
&lt;/h2&gt;

&lt;p&gt;~1,000 MAU. Your first real logo just signed, on the condition they log in through their own identity provider, with their users provisioned automatically. &lt;strong&gt;One SAML connection, with SCIM:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vendor&lt;/th&gt;
&lt;th&gt;Plan /mo&lt;/th&gt;
&lt;th&gt;SSO /mo&lt;/th&gt;
&lt;th&gt;SCIM /mo&lt;/th&gt;
&lt;th&gt;All-in /mo&lt;/th&gt;
&lt;th&gt;All-in /yr&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authagonal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$29&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$29&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$319&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clerk&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;$0 &lt;em&gt;(first free)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;$300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WorkOS&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$125&lt;/td&gt;
&lt;td&gt;$125&lt;/td&gt;
&lt;td&gt;$250&lt;/td&gt;
&lt;td&gt;$3,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth0&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;incl.&lt;/td&gt;
&lt;td&gt;incl.&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At this size, three of the four sit near zero. Clerk's first connection is free (its plan is $25). Auth0's &lt;em&gt;Free&lt;/em&gt; tier now bundles one enterprise connection, self-service SSO, and SCIM for $0, up to 25,000 MAU. Genuinely free, and credit for it (the catch is further down). We're $29, all-in. Only WorkOS breaks ranks: $3,000/yr already, all of it the per-connection SSO-and-SCIM tax. So far the tax is mostly hiding: add a second enterprise customer and watch it surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario 2: a handful of enterprise customers
&lt;/h2&gt;

&lt;p&gt;~10,000 MAU, SSO now part of the sales motion. &lt;strong&gt;Three SAML connections, with SCIM:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vendor&lt;/th&gt;
&lt;th&gt;Plan /mo&lt;/th&gt;
&lt;th&gt;SSO /mo&lt;/th&gt;
&lt;th&gt;SCIM /mo&lt;/th&gt;
&lt;th&gt;All-in /mo&lt;/th&gt;
&lt;th&gt;All-in /yr&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authagonal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$149&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$149&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1,639&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clerk&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;$150 &lt;em&gt;(1 free + 2×$75)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$175&lt;/td&gt;
&lt;td&gt;$2,100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WorkOS&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$375&lt;/td&gt;
&lt;td&gt;$375&lt;/td&gt;
&lt;td&gt;$750&lt;/td&gt;
&lt;td&gt;$9,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth0&lt;/td&gt;
&lt;td&gt;quote&lt;/td&gt;
&lt;td&gt;quote&lt;/td&gt;
&lt;td&gt;quote&lt;/td&gt;
&lt;td&gt;quote&lt;/td&gt;
&lt;td&gt;quote&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now we're the cheapest line in the table, not by trimming features but because the base is all you pay. Every one of those three connections is a paying customer, so the per-connection meter speeds up exactly as SSO starts doing its job. WorkOS's separate SCIM line has already doubled it to $9,000/yr; ours is still $1,639, SAML and SCIM included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario 3: SSO is now table stakes
&lt;/h2&gt;

&lt;p&gt;~50,000 MAU. Ten enterprise customers, &lt;strong&gt;ten SAML connections, with SCIM.&lt;/strong&gt; Nobody evaluates you without it anymore:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vendor&lt;/th&gt;
&lt;th&gt;Plan /mo&lt;/th&gt;
&lt;th&gt;SSO /mo&lt;/th&gt;
&lt;th&gt;SCIM /mo&lt;/th&gt;
&lt;th&gt;All-in /mo&lt;/th&gt;
&lt;th&gt;All-in /yr&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authagonal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$339&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$339&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$3,729&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clerk&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;$675 &lt;em&gt;(1 free + 9×$75)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$700&lt;/td&gt;
&lt;td&gt;$8,400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WorkOS&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$1,250&lt;/td&gt;
&lt;td&gt;$1,250&lt;/td&gt;
&lt;td&gt;$2,500&lt;/td&gt;
&lt;td&gt;$30,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth0&lt;/td&gt;
&lt;td&gt;quote&lt;/td&gt;
&lt;td&gt;quote&lt;/td&gt;
&lt;td&gt;quote&lt;/td&gt;
&lt;td&gt;quote&lt;/td&gt;
&lt;td&gt;quote&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the top row against the bottom one. Authagonal's &lt;strong&gt;entire&lt;/strong&gt; annual bill (platform, unlimited SAML, SCIM, MFA, audit, branding, the lot) is &lt;strong&gt;$3,729&lt;/strong&gt;. WorkOS's SSO-and-SCIM tax &lt;strong&gt;alone&lt;/strong&gt; is &lt;strong&gt;$30,000&lt;/strong&gt;: more than eight times our whole invoice, before WorkOS has charged a cent for the platform itself, and it doesn't need to, because the tax &lt;em&gt;is&lt;/em&gt; the platform. That ~$26,000-a-year gap is exactly what was going unmentioned when we only showed the SSO line. The bytes are identical to a password login; the code was written once and shipped to everyone. You'd be paying, every year, for twenty checkboxes and a platform you could have had for roughly an eighth of the price.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;On the numbers.&lt;/strong&gt; &lt;em&gt;Plan&lt;/em&gt; is the MAU-tiered platform price: the one thing we, and most vendors, legitimately charge more for as you grow. &lt;strong&gt;WorkOS&lt;/strong&gt; includes user management free to 1M MAU, so its whole bill is the per-connection tax. &lt;strong&gt;Clerk&lt;/strong&gt; Pro is $25/mo with MAU largely included at these volumes (modest overage possible at 50k); its first SSO connection is free, then ~$75 each, and SCIM comes free with a connection. &lt;strong&gt;Auth0&lt;/strong&gt; changed its model recently enough that it needs its own paragraph, just below. Authagonal's annual figures are what it actually bills: 11× the monthly price, because annual plans get a month free (so $29/mo is $319/yr, not $348). Competitor annuals are monthly × 12; if a vendor discounts annual billing we haven't vetted it, so their real totals may dip a little, nowhere near enough to close the gap.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;One more on Auth0, because its model is the subtlest.&lt;/strong&gt; Auth0 now bundles one enterprise connection, SSO, and SCIM into its &lt;em&gt;Free&lt;/em&gt; plan, free up to 25,000 MAU. At a single connection that genuinely costs nothing, and credit for it. But its paid &lt;em&gt;consumer&lt;/em&gt; tiers strip them back out; the footnote tells you to &lt;em&gt;"upgrade to B2B"&lt;/em&gt; to keep SSO. And B2B runs several times the consumer price for the same users (Essentials starts near $150/mo against ~$35), so Auth0's real SSO tax isn't per-connection, it's the &lt;strong&gt;B2B premium&lt;/strong&gt;: a multiple on the base price for the privilege of selling to businesses. That base is slider-priced by MAU and goes custom above ~20k users, which is why Auth0 reads &lt;code&gt;quote&lt;/code&gt; in our 10k and 50k rows instead of an interpolated guess.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Two more vendors meter on a different axis, so they're not in the tables: same tax, different hat. **Okta&lt;/em&gt;* licenses SSO per user, ~$2/user à la carte, scaling with the humans behind each connection rather than the connection count. &lt;strong&gt;Duende IdentityServer&lt;/strong&gt; sells SAML as a flat add-on, ~$1,500/yr on top of a ~$5,750/yr license, and then you host, patch, scale, and build the admin UI, MFA, and audit trail yourself. Cheap-looking line item, enormous real bill.)*&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why do I have to pay extra for SSO?&lt;/strong&gt;&lt;br&gt;
For most vendors, you don't pay extra because SSO costs them more. It doesn't. You pay extra because security and compliance features are the ones you're least able to refuse, so they're the most profitable to gate. SAML is a settled, twenty-year-old standard; the marginal cost of enabling it for one more tenant rounds to zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the "SSO tax"?&lt;/strong&gt;&lt;br&gt;
The practice of charging a premium (usually per connection, often $75–$125/mo each, or by forcing an Enterprise-tier upgrade) to turn on single sign-on. There's a public list of offenders at sso.tax. It's a tax on doing the responsible thing, collected at the moment you have the least leverage to say no.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much does SSO cost at Auth0, WorkOS, and Clerk?&lt;/strong&gt;&lt;br&gt;
On their published June 2026 rates: WorkOS bills about $125/mo per SSO connection (plus another ~$125 for SCIM); Clerk gives you one free connection, then about $75/mo each after. Auth0 is the odd one out: it includes one enterprise connection and SCIM free up to 25k MAU, then removes SSO from its paid self-serve plans and routes production or multi-connection use to a quote-only B2B track. At ten enterprise customers, WorkOS and Clerk run roughly $15k and $8.1k a year for SAML alone; Auth0 won't publish a number for it. Figures change, so check each vendor's current pricing page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there a free tier?&lt;/strong&gt;&lt;br&gt;
Yes. Free up to 250 monthly active users, every feature included, and (unlike every other free tier) &lt;strong&gt;unlimited&lt;/strong&gt; SSO/SAML connections, not just one. It's capped only on scale: cross 250 MAU and you move onto a paid plan. No credit card, no SLA, community support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I have to upgrade my plan to add another OAuth client or application?&lt;/strong&gt;&lt;br&gt;
Not with us. Applications aren't a paywalled axis, so you register one per app or service without changing tiers. Several providers do cap or meter clients (especially machine-to-machine ones), which is what forces the choice between reusing a client you shouldn't and paying for the next tier.&lt;/p&gt;




&lt;p&gt;You can tell a lot about a company from what it charges you for. Most auth vendors charge for things that cost them nothing: a SAML flag, a SCIM flag, a row for one more client. Ours comes down to a single line: pay for how big you get, not for which switches you're allowed to flip.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://authagonal.io/compare" rel="noopener noreferrer"&gt;Here's exactly who charges for what&lt;/a&gt;, vendor by vendor. SSO's on our side of the table, at no extra charge, and so is the eleventh client you didn't know you'd need. &lt;a href="https://authagonal.io/pricing" rel="noopener noreferrer"&gt;See it priced for your numbers.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>auth</category>
      <category>pricing</category>
      <category>sso</category>
      <category>ssotax</category>
    </item>
    <item>
      <title>Quitter Duende auto-hébergé : ce qui migre, et ce que vous cessez d'exploiter</title>
      <dc:creator>authagonal</dc:creator>
      <pubDate>Thu, 02 Jul 2026 10:03:02 +0000</pubDate>
      <link>https://dev.to/authagonal/quitter-duende-auto-heberge-ce-qui-migre-et-ce-que-vous-cessez-dexploiter-39a8</link>
      <guid>https://dev.to/authagonal/quitter-duende-auto-heberge-ce-qui-migre-et-ce-que-vous-cessez-dexploiter-39a8</guid>
      <description>&lt;p&gt;Duende IdentityServer est un logiciel vraiment excellent : si vous voulez posséder votre couche d'identité de bout en bout, c'est la référence en .NET. Mais cette « possession », c'est justement tout le coût : vous l'hébergez, vous la patchez, vous la mettez à l'échelle et vous construisez tout ce qui &lt;em&gt;gravite autour&lt;/em&gt;, l'interface d'administration, le MFA, les journaux d'audit, l'image de marque, une page de statut, la gestion des utilisateurs et des rôles. À un moment donné, une équipe décide qu'elle préfère ne pas exploiter d'IdP, et la seule question qui compte est de savoir à quel point la migration est douloureuse. Voici ce qui migre réellement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce que vous exploitez vraiment
&lt;/h2&gt;

&lt;p&gt;Auto-héberger Duende vous rend responsable de bien plus que des points de terminaison de tokens :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;L'IdP lui-même&lt;/strong&gt; : hébergement, mise à l'échelle, patching et la licence (le SAML est un module payant en supplément).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tout ce qui l'entoure et que vous avez construit vous-même&lt;/strong&gt; : portail d'administration, enrôlement MFA, journalisation d'audit, image de marque personnalisée, une page de statut, gestion des utilisateurs et des rôles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;C'est généralement cette deuxième liste qui consomme le plus de temps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce qui migre, et c'est essentiellement mécanique
&lt;/h2&gt;

&lt;p&gt;Duende conserve sa configuration dans SQL (la ConfigurationDb) et ses utilisateurs dans ASP.NET Identity. Une migration lit les deux :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clients&lt;/strong&gt; → clients OAuth, y compris les URI de redirection et de déconnexion, les origines CORS, les types d'autorisation, la sémantique d'usage et d'expiration des refresh tokens, et les durées de vie des device codes. Les clients désactivés sont importés désactivés ; les secrets expirés sont ignorés avec un avertissement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scopes&lt;/strong&gt; → les ApiScopes et IdentityResources se mappent directement. La couche intermédiaire &lt;code&gt;ApiResource&lt;/code&gt; de Duende (une audience plus une liste de claims partagée) s'aplatit sur le modèle plus simple : le nom de la ressource devient une audience sur chaque client qui utilise un scope membre, et ses claims fusionnent avec ces scopes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Utilisateurs&lt;/strong&gt; → la bonne nouvelle : les hachages de mot de passe d'ASP.NET Identity V3 (et le bcrypt hérité) &lt;strong&gt;se vérifient nativement et sont rehachés à la première connexion&lt;/strong&gt;. Aucune réinitialisation de mot de passe, aucun ticket de support, rien que vos utilisateurs ne remarquent. (C'est la partie réellement difficile quand on quitte Auth0 : avec Duende, ça fonctionne tout simplement, parce que c'est le même hachage que celui que votre application utilise déjà.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Les rôles et affectations, les connexions externes et les fournisseurs d'identité OIDC&lt;/strong&gt; sont tous repris. Les fournisseurs SAML sont signalés pour que vous les reconfiguriez de l'autre côté.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  L'aspect stabilité de l'identité
&lt;/h2&gt;

&lt;p&gt;Comme pour tout changement d'IdP, ce qu'il faut bien gérer, c'est de ne pas changer le &lt;code&gt;sub&lt;/code&gt; de l'utilisateur ni le &lt;code&gt;client_id&lt;/code&gt; : les refresh tokens en aval, les lignes SCIM dans les IdP des clients et les identifiants d'utilisateur stockés dans votre propre base de données y font tous référence. L'importateur préserve les deux. Et si l'un de vos propriétaires de portail existe aussi comme utilisateur Duende sous un identifiant différent, il les réconcilie (en basculant vers le &lt;code&gt;sub&lt;/code&gt; Duende) par étapes échelonnées et réversibles, plutôt que de laisser un propriétaire à moitié migré incapable de se connecter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Une parenthèse sur le test des migrations (un piège .NET)
&lt;/h2&gt;

&lt;p&gt;Nous testons l'importateur contre une vraie base de données Duende alimentée en données, pas contre des mocks, et ça a porté ses fruits. ASP.NET Identity stocke &lt;code&gt;AspNetUsers.LockoutEnd&lt;/code&gt; sous forme de &lt;code&gt;datetimeoffset&lt;/code&gt;, et le lire avec &lt;code&gt;reader.GetDateTime()&lt;/code&gt; lève une &lt;code&gt;InvalidCastException&lt;/code&gt; sur ce type. Ainsi, un seul utilisateur verrouillé aurait fait échouer l'import de la &lt;em&gt;totalité&lt;/em&gt; des utilisateurs. On ne le découvre qu'en exécutant l'importateur contre des données réelles contenant un utilisateur verrouillé. Si vous évaluez un outil de migration, voilà la question qui mérite d'être posée : est-il testé contre une base de données peuplée, ou simplement mocké contre l'API source ?&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce que vous cessez de faire
&lt;/h2&gt;

&lt;p&gt;L'intérêt de la migration, ce n'est pas la migration elle-même : c'est tout ce qui vient après. SAML, SCIM, MFA, journaux d'audit, domaines personnalisés et image de marque sont inclus au lieu d'être des choses que vous construisez et exploitez, et le patching et la mise à l'échelle de l'IdP cessent d'être votre problème. Si l'auto-hébergement de Duende était une décision délibérée (« nous voulons un contrôle total ») et qu'elle l'est toujours, restez : c'est un choix parfaitement valable. Ceci s'adresse au moment où exploiter un IdP n'est plus là où vous voulez passer votre temps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Si vous l'envisagez
&lt;/h2&gt;

&lt;p&gt;La migration est essentiellement mécanique, les mots de passe sont repris sans réinitialisation, et l'aperçu est en lecture seule : pointez-le vers votre base de données Duende et il vous montre exactement ce qui serait importé avant que quoi que ce soit ne soit écrit.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://authagonal.io/migrate/duende" rel="noopener noreferrer"&gt;Migrer depuis Duende&lt;/a&gt;&lt;/p&gt;

</description>
      <category>duende</category>
      <category>identityserver</category>
      <category>dotnet</category>
      <category>oidc</category>
    </item>
    <item>
      <title>Our service discovery caught its own failure and switched itself off</title>
      <dc:creator>authagonal</dc:creator>
      <pubDate>Wed, 01 Jul 2026 23:13:01 +0000</pubDate>
      <link>https://dev.to/authagonal/our-service-discovery-caught-its-own-failure-and-switched-itself-off-105p</link>
      <guid>https://dev.to/authagonal/our-service-discovery-caught-its-own-failure-and-switched-itself-off-105p</guid>
      <description>&lt;p&gt;We had a three-replica cluster that kept disagreeing with itself. Background jobs ran two and three times over. The answer wasn't in the logs; it was in our own code. The peer-discovery routine had a catch block, and the comment inside it said, more or less, "multicast failed, discovery disabled." Our service discovery was catching its own failure and quietly turning itself off, and it had been doing exactly that in production from day one.&lt;/p&gt;

&lt;p&gt;This is the story of why a clustering protocol that's correct on a server in a rack is the wrong tool the moment you put it on a managed platform, and why the fix was to delete it rather than repair it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the cluster is actually for
&lt;/h2&gt;

&lt;p&gt;An auth server runs as more than one replica for availability. The replicas are mostly independent: any of them can validate a token or check a password. But a few jobs must run &lt;em&gt;once&lt;/em&gt;, not once per replica. The data-retention sweep that deletes expired records. The pass that fires customer webhooks. Anything that reaches out and has a side effect. For those you need two things the cluster has to agree on: who the members are, and which one of them is the leader that runs the singleton work.&lt;/p&gt;

&lt;p&gt;Our original answer was the classic one: gossip. Each node chatters with its peers, membership is an emergent property of who's reachable, and the group elects a leader from the agreed set. It's a beautiful design. It also assumes the nodes can find each other, and the way the old code found them was multicast: shout on the local network segment and see who answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it broke, silently
&lt;/h2&gt;

&lt;p&gt;Managed Kubernetes does not carry multicast. Neither does almost any cloud network you don't build yourself. The shout went out and nothing came back, every time. So discovery "failed," the catch block fired, and each node concluded it was alone in the world.&lt;/p&gt;

&lt;p&gt;At one replica that's harmless. At three it's split-brain by construction: three nodes, each certain it's the only member, each electing itself leader, each running the singleton work. The retention sweep ran on all three. The webhook pass fired every customer's webhook three times. None of it threw an error, because from each lonely node's point of view everything was fine. The bug wasn't a crash; it was three programs each behaving perfectly correctly in a world they had wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix was to stop discovering peers at all
&lt;/h2&gt;

&lt;p&gt;Here's the reframe that made the whole thing collapse to a few lines: we were trying to rebuild, with a chatty protocol, a fact the platform already stores for us with strong consistency. We run on a cloud that hands us a strongly-consistent store. Leader election doesn't need consensus among peers if there's already one place everyone can agree on.&lt;/p&gt;

&lt;p&gt;So leadership became a &lt;strong&gt;blob lease&lt;/strong&gt;. One blob, one lease. Whoever holds the lease is the leader. The lease has a timeout, so a leader that dies stops renewing and the lease frees itself for the next taker. There is no peer discovery, no quorum math, no multicast, and no catch block waiting to disable it. Coordination and events ride a small table that acts as a bus between the nodes.&lt;/p&gt;

&lt;p&gt;The property I didn't expect to love: you can open Storage Explorer and &lt;em&gt;see&lt;/em&gt; the cluster's mind. Who holds the lease right now. What's queued on the bus. Membership stopped being something you infer from a protocol's behavior and became a row you can read. When the thing that decides who runs your destructive jobs is a value you can look at, debugging stops being archaeology.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part where we deleted a clever thing on purpose
&lt;/h2&gt;

&lt;p&gt;While we were in there, we deleted a distributed rate-limiter built on a conflict-free replicated data type, a CRDT that merged per-node counters into a global count without coordination. It was genuinely elegant. It was also solving a problem we'd moved. The global rate limit belongs at the edge, where requests arrive, not reconstructed inside the cluster with a data structure that needs a paragraph to explain. Out it came.&lt;/p&gt;

&lt;p&gt;Deleting working code feels like a loss until you count what you stop maintaining. We removed the gossip layer, the multicast assumption, the self-disabling catch block, and the CRDT, and replaced all of it with a lease and a table. The line count went down and the number of states the system can be in went down with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson, factored out
&lt;/h2&gt;

&lt;p&gt;Gossip and multicast membership are the right tools on bare metal or a flat network you control. They are the wrong tools on a platform whose network won't carry the very mechanism they depend on, and the failure mode isn't a loud crash; it's a quiet fallback that looks like it's working. Before you port a distributed-systems pattern onto managed infrastructure, ask what it assumes about the network, and ask what the platform already gives you for free. Ours was already running a strongly-consistent store. A lease in that store was a simpler, more correct leader election than the protocol we'd been carrying, and it's one we can watch.&lt;/p&gt;

&lt;p&gt;If you're running auth, the parts that hold your keys and decide who runs your destructive jobs should be the most boring and the most inspectable things you own. We made ours boring. It was an upgrade.&lt;/p&gt;

&lt;p&gt;And if you would rather not run a clustering protocol at all, that is the pitch: &lt;a href="https://authagonal.io" rel="noopener noreferrer"&gt;Authagonal&lt;/a&gt; keeps this boring so you never have to debug your own split-brain.&lt;/p&gt;

</description>
      <category>auth</category>
      <category>distributedsystems</category>
      <category>clustering</category>
      <category>leaderelection</category>
    </item>
  </channel>
</rss>
