Wallet login for Keycloak. Sign-In With Ethereum (SIWE) and Solana (SIWS) β EOA and smart-contract wallets (EIP-1271, EIP-6492) β as a native Keycloak Authenticator. No wallet SDK, no third-party auth service, your existing passwords/MFA/social logins untouched.
Docker is the only requirement β no Java, no build:
git clone https://github.com/aliIsazadeh/keycloak-wallet-authenticator.git
cd keycloak-wallet-authenticator
docker compose -f demo/docker-compose.yml upThen open http://localhost:8080/realms/wallet-demo/account and sign in with MetaMask (Ethereum) or Phantom (Solana). Details, admin credentials, and teardown: demo/README.md.
Every existing "Sign-In With Ethereum + Keycloak" project I could find was an abandoned proof-of-concept β pinned to dead testnets, EOA-only, no smart-contract wallet support, and untested against a real Keycloak. Teams that already run Keycloak for their identity and want to add wallet login are left wiring up a second OIDC server or a fragile reverse-proxy hack.
Keycloak Wallet Authenticator is the version that isn't a toy: a real Authenticator SPI plugin, built on a framework-free verification core, tested end-to-end against a live Keycloak in Testcontainers.
- Adds wallet login to your existing Keycloak as one Authenticator in a browser flow. Your passwords, MFA, and social logins stay exactly as they are β this is additive, not a replacement.
- Verifies the signature, not just the address. It recovers the signer, checks it matches the address claimed inside the signed message, and validates every server-authoritative field (domain, URI, nonce, issued-at / expiration) before anyone is logged in.
- Provisions a stable Keycloak user keyed on wallet identity, so switching chains never splits one wallet owner into multiple users.
| Capability | Status |
|---|---|
| SIWE β Sign-In With Ethereum (EIP-4361) | β |
| SIWS β Sign-In With Solana | β |
EVM chains (eip155) |
β |
| Solana (Ed25519) | β |
| EOA (externally-owned) wallets | β |
| EIP-1271 deployed smart-contract wallets | β |
| EIP-6492 counterfactual (pre-deploy) smart-contract wallets | β |
| Native (non-browser) direct-grant flow for mobile/native apps | β |
| Single-use nonce (replay-protected) | β |
| Domain / URI binding (anti-phishing) | β |
| Tested against real Keycloak (Testcontainers) | β |
| Zero wallet-SDK dependency | β |
| Zero external auth service | β |
Two round trips, and the server is authoritative on domain, nonce, and expiry:
- Challenge. When the Web3 Wallet Authenticator runs, it generates a 128-bit
CSPRNG nonce, stores it in the Keycloak authentication-session note (no Redis, no
extra infrastructure), and renders a login page that talks to the browser wallet
(
window.ethereum/ Phantomwindow.solana). - Verify. On postback it parses the SIWE/SIWS message, requires the message nonce to equal the stored note (single-use β the note is removed on success), validates domain / URI / timestamps (Β±5 min skew), verifies the signature, and confirms the recovered signer equals the claimed address. Only then is a Keycloak user provisioned or logged in.
The Keycloak username is the canonical identity key namespace:address, so the same
wallet is always the same user regardless of chain. Address, namespace, and chain ID
are stored as user attributes (w3auth_address, w3auth_namespace, w3auth_chainId).
For EVM smart-contract wallets, the plugin talks to an Ethereum node over Java 21's
native HttpClient (HttpChainClient) β deliberately avoiding web3j-core so
Keycloak's server classpath stays clean.
Native and mobile apps that can't drive a browser flow can use the direct-grant wire contract instead: see docs/native-api.md.
Requirements: Keycloak 25+, Java 21, an EVM RPC endpoint only if you need smart-contract wallet support.
Grab the prebuilt fat JAR from the latest release (SHA-256 checksums included), or build it yourself:
./gradlew :w3auth-keycloak-plugin:jar
# produces w3auth-keycloak-plugin/build/libs/w3auth-keycloak-plugin-<version>.jarcp w3auth-keycloak-plugin-*.jar /opt/keycloak/providers/
/opt/keycloak/bin/kc.sh build
/opt/keycloak/bin/kc.sh startIn the admin console: Authentication β Flows, duplicate the browser flow, add an execution, choose Web3 Wallet Authenticator, and bind the flow to your realm (or an application) as the browser flow.
On the authenticator's config, set:
| Setting | Key | Default | Notes |
|---|---|---|---|
| Expected Domain | expected-domain |
localhost:8080 |
Must match the SIWE/SIWS domain. This is the anti-phishing control β set it to your real domain in production. |
| Expected URI | expected-uri |
http://localhost:8080 |
Must match the message uri. |
| Ethereum RPC URL | ethereum-rpc-url |
(empty) | JSON-RPC endpoint for EIP-1271 / EIP-6492 verification. Leave empty and smart-contract wallets are disabled β EVM falls back to EOA-only. |
That's it. Users can now log in with a wallet.
Configuration:
expected-domainmust be host:port, exactly. EIP-4361 requires the SIWE/SIWSdomainfield to equal the requesting page's origin authority β the host, plus the port when it's non-default β exactly as it appears in the browser's address bar.expected-domainmust match that, not just the bare hostname. If it's wrong, the wallet doesn't error clearly; you'll see something like "the domain in the sign-in message does not match the requesting app's origin" with no mention of Keycloak or this plugin. Example: serving onhttp://localhost:8080needsexpected-domain = localhost:8080, notlocalhost.
Users provisioned through wallet login get placeholder profile data: first name
"Wallet", last name set to the wallet's address, and a non-deliverable email
(<address>@wallet.invalid) with emailVerified set to true. This is
deliberate β Keycloak's default declarative user profile requires
first name/last name/email, and a bare user without them gets diverted to an
"Update Account Information" screen after a valid wallet signature, defeating
the point of wallet-only login. The placeholders keep login to one round trip
on any realm, with no extra realm configuration required.
If you want to collect a real email or name from wallet users instead β for example to send them account notifications β the plugin only sets these placeholders once, at first provisioning; it never overwrites them on later logins. Update the user's profile yourself afterward (admin console, Admin REST API, or your own post-login flow), or adjust your realm's User profile configuration to require the fields you actually want collected.
- Domain binding is enforced β a signature valid for another site's domain is rejected. This is the anti-phishing / cross-site-replay control.
- Single-use nonces β the challenge nonce is consumed on verify and cannot be replayed.
- Claim validation, not address recovery β the recovered signer must equal the address inside the signed message; recovery alone authenticates nobody.
- Server-authoritative fields β domain, URI, and expiry are checked against server config and policy, with a Β±5 minute clock-skew tolerance.
Found a vulnerability? See SECURITY.md. Please do not open a public issue for security reports.
This repo is a multi-module Gradle build (Java 21, Kotlin DSL, centralized version catalog):
| Module | Role |
|---|---|
w3auth-core |
Framework-free verification engine: identity, challenge, SIWE/SIWS parsing, signature verification, sessions. No Spring / JPA / Redis on its classpath. |
w3auth-keycloak-plugin |
The Keycloak Authenticator SPI plugin β the product, and this README's focus. Fat JAR, BouncyCastle excluded (Keycloak provides it). |
demo |
The 2-minute Docker quickstart above. |
examples/self-hosted-rest-api |
Reference example, not the product. A standalone Spring Boot REST auth API built on the same core, showing how to self-host wallet login without Keycloak. See its README. |
The same verification engine powers both the plugin and the reference REST API β the architecture is protocol-driven, so no wallet vendor's code ever leaks into it.
The deeper reasoning β why identity is modeled as CAIP-10 namespace:address, why
nonces are consumed atomically, the EIP-6492 dispatch ordering, why HS256 for
short-lived tokens β lives in docs/ARCHITECTURE.md.
Not running Keycloak? The repo ships a small reference example that
demonstrates the same verification core (w3auth-core) self-hosted as a
standalone Spring Boot REST API, with its own Postgres/Redis adapters and
challenge/verify/refresh/logout endpoints. It is a demonstration, not the
product β the Keycloak plugin above remains the headline. If you already run
Keycloak, ignore this; use the plugin.
See examples/self-hosted-rest-api
to run it.
Apache-2.0. See LICENSE.
I build production-grade wallet and crypto-identity authentication infrastructure. If your team needs wallet login in Keycloak (or elsewhere), or custom web3 identity integration, I'm available for consulting and integration work.
- GitHub: @aliIsazadeh
- Contact: isazadhali@gmail.com Β· LinkedIn
