karhal/web3-connect
Karhal Web3 Connect brings crypto wallet authentication to Laravel. Let users sign in with MetaMask, WalletConnect, and other Web3 wallets, verify signatures server-side, and link wallet addresses to existing accounts—no passwords required.
This is a Symfony bundle, not a Laravel package, and the repository is archived. Treat it as a reference implementation to mine for the wallet-login flow rather than something you composer require into a Laravel app. The realistic day-one move is to read the source, lift the challenge/signature logic, and re-implement it behind a Laravel guard.
Minimal path:
composer.json first. Confirm the PHP version, the signature-verification dependency (typically kornrunner/keccak plus an elliptic-curve or web3.php-style library), and the Symfony version constraints. This tells you whether the crypto layer is portable or entangled with Symfony services.Security\Authenticator, a login controller, or a GuardAuthenticator. That file contains the entire flow: nonce issuance, message construction, signature verification, and user lookup.personal_sign / EIP-191) and whether it follows EIP-4361 (Sign-In with Ethereum). If it's a custom format, note the fields — address, nonce, domain, statement, chain ID, issued-at.Cache or session store.First use case to target: "Connect Wallet → Sign → Authenticate." The user clicks connect, your app issues a single-use nonce, the wallet signs a human-readable message, the server recovers the address, and you log in (or create) the matching user. That is the whole product; everything else is hardening.
Challenge/response is the core loop. Generate a cryptographically random nonce, store it server-side with a short TTL (2–5 minutes), and bind it to the session or to the claimed address. On the callback, consume it atomically — delete-on-read — so a captured signature can't be replayed.
Separate the three flows. They look similar but have different security requirements:
Model it as a Laravel guard. A viaRequest guard or a dedicated WalletAuthController that calls Auth::login($user) keeps it idiomatic. Register a wallet guard in config/auth.php, resolve the user by a wallet_address column, and let middleware do the rest. Don't bolt this onto the existing session guard.
Server-side verification is non-negotiable. The client sends {address, signature, nonce}. The server rebuilds the exact message it issued, recovers the signer, and compares checksummed addresses. Never accept "the wallet told me it's 0xabc…" as proof. In Laravel this is a small WalletSignatureVerifier service wrapping kornrunner/keccak and an ecrecover implementation — the same primitives the bundle uses, minus the Symfony wiring.
Adopt EIP-4361 (SIWE) message formatting even if the bundle uses something custom. Wallets, libraries, and future-you all understand it. The message includes domain, address, statement, URI, nonce, chain ID, and timestamps — which gives you replay protection for free if you validate domain and chain ID on the server.
Store the nonce where you can expire it. Laravel's cache with a put($key, $nonce, now()->addMinutes(5)) and pull() on verification is cleaner than a database table. Key it by session ID or by a server-issued challenge ID returned to the client.
Testing with deterministic keys. Use Hardhat/Anvil's well-known private keys and precomputed signatures in feature tests. Fix the nonce in the test, assert the recovered address, and assert a second use of the same signature fails. This is the only reliable way to catch encoding bugs.
Extension points to design in from the start: a chain allowlist (reject signatures from unexpected chainId), a pluggable verifier interface (so you can add EIP-1271 contract-wallet support later), and events like WalletLinked / WalletAuthenticated so you can hook in audit logging.
NOASSERTION. There is no clear grant to copy or redistribute. Get a legal read before lifting source verbatim; re-implementing the flow from the published standard (EIP-4361/EIP-191) is safer.services.yaml, bundle registration, and GuardAuthenticator have no Laravel equivalent. Port the logic, not the structure.=== on raw input.personal_sign vs eth_sign. Wallets prefix the message differently (EIP-191). If you rebuild the message incorrectly by one byte, recovery silently yields the wrong address. Log the exact message hash when debugging.ecrecover. Smart-contract wallets (Safe, Argent) return a boolean from isValidSignature instead of an ECDSA signature. Plain recovery will reject them. Decide early whether you support them; if yes, plan an on-chain call or a verifier abstraction.v values. Some libraries return v as 27/28, others as 0/1. Normalize before recovery or you'll get sporadic failures that only show up with certain wallets.\x19Ethereum Signed Message:\n prefix.chainId at signature time and validate it against your allowlist rather than trusting the current connection.How can I help you explore Laravel packages today?