Skip to main content
Jeff Patzer
Phase Two

Jeff is the co-founder and COO of Phase Two, and has been actively contributing to Keycloak and its open-source extension ecosystem for many years. Prior to Phase Two, he built a successful career as an engineering director at a major CDN.

View all authors

Keycloak infinite redirect loop at login

· 8 min read
Jeff Patzer
Phase Two

A Keycloak infinite redirect loop at login almost always means one thing: a cookie Keycloak set on the way to the login page did not come back on the way out of it. Keycloak has no session to resume, so it starts the flow again, and the browser bounces between your application and /protocol/openid-connect/auth until it gives up.

The reason this is so common is in the Set-Cookie headers. Keycloak 26.8.0 marks every cookie in the login flow Secure; SameSite=None, unconditionally — there is no setting that relaxes it. A browser will not store a Secure cookie that arrives over plain http://, and it will not store SameSite=None without Secure. So the loop is usually not an OIDC problem at all. It is a transport problem that the OIDC layer reports as a lost session.

Migrating from Auth0 to Keycloak: a complete walkthrough

· 20 min read
Jeff Patzer
Phase Two

Migrating from Auth0 to Keycloak is three separate jobs wearing one name. Profiles move easily — Auth0's export job gives you newline-delimited JSON and Keycloak's partial-import endpoint takes it. Passwords do not move at all: Auth0 hashes with bcrypt, Keycloak 26.7.4 ships four password-hashing providers and none of them is bcrypt, so you either reset every password or stand up a bridge that verifies against the old hashes on first login. Your Rules and Actions have to be rewritten, because there is no equivalent runtime — some of them become authentication-flow configuration with no code at all, and some become a Java SPI.

The part that costs people a weekend is none of those. It is that Keycloak's admin API answers 201 Created to three different malformed migrations and then quietly loses the data. Everything below was run against Keycloak 26.7.4 on PostgreSQL 16.

Keycloak 403 Forbidden: client, realm and admin API causes

· 12 min read
Jeff Patzer
Phase Two

A Keycloak 403 Forbidden always means the same thing: your token was accepted, and the permissions attached to it were not enough. That is the whole difference from a 401, which means the token itself was rejected — missing, expired, malformed, or signed by another realm. Checking which of the two you have is the fastest useful thing you can do, because 401 sends you to the token and 403 sends you to role mappings, and they share no fixes.

After that, the question is which surface returned it. The Keycloak admin console, the Admin REST API and your own application all return 403 for unrelated reasons, and the response body tells you which one you are looking at without any further digging.

JWKS explained: rotation, caching, validating tokens

· 14 min read
Jeff Patzer
Phase Two

A JWKS — JSON Web Key Set — is a JSON document containing a list of public keys, each encoded as a JWK (JSON Web Key). Its purpose is to let anyone verifying a signed token fetch the right public key over HTTP instead of having it configured by hand. The token names the key it was signed with in its kid header; the verifier looks that kid up in the set.

That is the whole idea, and it buys one specific thing: the signer can change keys without anyone who verifies its tokens changing configuration. Everything else about JWKS — the field names, the caching, the rotation ordering — follows from that.

This post covers what is actually in a JWK, where the JWKS URL comes from, what Keycloak publishes and what it deliberately does not, and the two directions the traffic flows in. Every number and every error string below came from a real run.

Tested against

Keycloak 26.7.4 in a container, plus Python 3.12 with cryptography 46 for the key arithmetic. JWK field definitions are from RFC 7517 and RFC 7518.

Keycloak Production Readiness Checklist

· 15 min read
Jeff Patzer
Phase Two

A Keycloak production readiness checklist has to answer two different questions, and most published ones only answer the first. Keycloak's start command refuses to boot until you settle two things, and it prints a clear error for each, so those are easy. The harder list is everything it will happily let you ship wrong: brute force protection is off, your audit log accepts forged IP addresses from anyone, event tables grow forever, and the readiness endpoint disagrees with your load balancer for the first few seconds of every restart.

Everything below was run against quay.io/keycloak/keycloak:26.7.3. Where a number appears, it came out of a terminal, not from memory.

Keycloak invalid_grant: the eight things it actually means

· 12 min read
Jeff Patzer
Phase Two

Keycloak returns invalid_grant for at least eight unrelated failures, and the error code itself tells you nothing. The useful field is error_description, which Keycloak fills in with a short string that maps almost one-to-one onto a cause:

{"error":"invalid_grant","error_description":"Code not valid"}

invalid_grant is OAuth's designated bucket for "the grant you presented is no good", so Keycloak uses it for expired codes, replayed codes, PKCE mismatches, rotated refresh tokens, dead sessions, revoked offline tokens, and bad passwords alike. Read the description, find it in the table below, stop guessing.

Everything here was run against Keycloak 26.7.3 on 2026-09-07, with realm defaults except where a test says otherwise.

Keycloak Custom Domains Can Now Serve App Association Files

· 5 min read
Jeff Patzer
Phase Two

Custom domains on Phase Two can now serve the files iOS and Android use to link a domain to a mobile app. Upload them from the dashboard and they are live in minutes — no deploy, no cluster restart.

That closes a gap that had nothing to do with Keycloak's capabilities and everything to do with where Keycloak sits in a mobile login flow.

Observability for Keycloak, with Zero Setup

· 5 min read
Jeff Patzer
Phase Two

Today we're launching Observability for dedicated Keycloak clusters — built directly into the Phase Two dashboard with zero setup. Requests, event data, and live logs are all there the moment your cluster is running. No agents to install, no log shippers to configure, no Prometheus, Grafana, or Loki stack to stand up and maintain.

Keycloak Themes: Custom Login, Admin, Account, and Email

· 12 min read
Jeff Patzer
Phase Two

Keycloak has exactly four theme types you can set on a realm: login, account, admin, and email. Each one is its own directory of templates, message bundles and static assets, packaged into a JAR and dropped into the server's providers/ directory. That is the whole extension point. It is also why custom Keycloak themes tend to become a standing maintenance cost: your templates are copies of upstream files, and upstream moves every release.

We've completely rebuilt our bundled Keycloak themes. What used to live as a tangle of custom pages inside a forked Keycloak repository is now a first-class Keycloakify-based React application that ships all four: login, admin, account, and email. The result is faster to maintain, far more capable, and dramatically better out of the box for the organizations using Phase Two today.

Switching themes is four realm fields. Set them in Realm settings → Themes in the admin console, or from the CLI. Verified against Keycloak 26.7.3:

kcadm.sh update realms/<your-realm> \
-s loginTheme=phasetwo-ui \
-s accountTheme=phasetwo-ui \
-s adminTheme=phasetwo-ui \
-s emailTheme=phasetwo-ui

Starting now, all Phase Two containers ship with this theme bundled. Any realm you create through the Phase Two Dashboard automatically gets the new login, admin, account, and email themes active, with no configuration required. The first time a user hits your login page or receives an email from your realm, it already looks good.

Phase Two Achieves ISO/IEC 27001 Certification

· 3 min read
Jeff Patzer
Phase Two

Phase Two is excited to announce that we are now ISO/IEC 27001 certified.

This milestone reflects how seriously we take security and compliance across our platform, operations, and internal processes. We completed this as a fast follow to our September 17, 2025 SOC 2 Type II compliance milestone, reaching full ISO/IEC 27001 certification just over six months later as part of our commitment to building a mature, enterprise-ready security program.

Learn more at our Trust Center: trust.phasetwo.io.