Download the PHP package sandermuller/socialite-solana without Composer
On this page you can find all versions of the php package sandermuller/socialite-solana. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package socialite-solana
Socialite Solana
Laravel Socialite provider for Sign-In With Solana (SIWS / CAIP-122).
This package adds a solana driver to Laravel Socialite. The wallet user signs a SIWS challenge message with their private key; the server verifies the Ed25519 signature and you get back a Socialite User keyed by the base58 public key.
- CAIP-122 / Phantom SIWS challenge format (domain, statement, URI, chain ID, nonce, issued-at, expiration-time, resources)
- Ed25519 verification via
sandermuller/solana-pubkey - Single-use nonce with configurable TTL — failed verifies don't burn the challenge
- Accepts base58 or base64 signatures
- Typed exception hierarchy so callers can map each failure case to its own UX
- Works from controllers, Livewire components, queue jobs, or console code — the HTTP wrappers are thin
- Pluggable challenge storage — session-backed default, cache-backed for API / Sanctum bearer-token flows, or your own implementation
- Optional PSR-3 logger injection for ops dashboards on failed-signature / expiry / malformed-input rates
Requirements
PHP 8.3+ and Laravel 11, 12, or 13. The ext-sodium PHP extension must be enabled.
Installation
Register the Socialite extension listener in app/Providers/AppServiceProvider.php::boot() (works on Laravel 11/12/13 — those skeletons no longer ship an EventServiceProvider):
On older Laravel apps that still have app/Providers/EventServiceProvider.php, the listener may instead live in the $listen array — the Event::listen() call above works on all supported versions.
Configuration
Add the solana block to config/services.php:
Usage
The Solana flow is not a redirect-based OAuth flow. The wallet returns a signed message synchronously, so the provider exposes both HTTP wrappers and framework-agnostic methods:
| Method | Signature | When to use |
|---|---|---|
challenge() |
Socialite::driver('solana')->challenge(): JsonResponse |
Controller routes; reads publicKey from the request, returns JSON. |
user() |
Socialite::driver('solana')->user(): User |
Controller routes; reads publicKey, signature, message, nonce from the request. |
buildChallengeFor(string $publicKey) |
returns array{message: string, nonce: string} |
Livewire, queue, console — no HTTP request needed. |
verifyCredentials(string $publicKey, string $signature, string $message, string $nonce) |
returns User |
Livewire, queue, console — no HTTP request needed. |
Calling redirect() throws BadMethodCallException — there's nothing to redirect to. Calling scopes() with non-empty input also throws; OAuth scopes are not applicable.
Controller routes
Livewire component
On the JS side, a small signMessageBase58() helper wraps the wallet adapter call so consumers don't repeat bs58.encode(signed.signature) at every site:
For UX, sharing a single error surface between sync (server validation) and async (wallet/network) errors keeps the component simple:
Granular error handling
Every authentication failure throws a specific subclass of SanderMuller\SocialiteSolana\Exceptions\SolanaAuthException, which itself extends \InvalidArgumentException. Catch the base class for a uniform 422, or the subclasses for per-case UX and rate-limit buckets:
Challenge storage
By default, the issued challenge lives in the Laravel session, so the browser's session cookie binds it to one device. For headless flows (Sanctum bearer tokens, native mobile, API clients) where no session exists, switch to the cache-backed store:
The cache store treats the 32-character nonce (~160 bits of entropy) as the unguessable handle, exactly the way short-lived bearer tokens work. Callers must keep the nonce secret between issue and verify.
For full control, implement SanderMuller\SocialiteSolana\Contracts\ChallengeStore and bind it in the container — the package prefers a container binding over the config string:
The interface is three methods:
Logging
The Provider accepts a PSR-3 LoggerInterface so you can ship SIWS auth events to your observability stack — useful for dashboards on failed-signature count, expiry rate, malformed-pubkey rate, and the like.
Resolution order:
- An instance passed via
setLogger()wins. - Otherwise a container-bound
Psr\Log\LoggerInterfaceis used. - Otherwise
NullLogger.
The recommended pattern is a scoped setLogger() call inside a small helper, not a global container binding. The Socialite manager memoizes drivers, so calling setLogger() on Socialite::driver('solana') once per request is idempotent across challenge() / user() / buildChallengeFor() / verifyCredentials() invocations:
[!WARNING] Binding
Psr\Log\LoggerInterfaceglobally in the container works, but it swaps the default logger for every package that resolves that contract — including Laravel internals — which is a wide blast radius for one provider's worth of observability. Prefer the scoped helper pattern above unless you genuinely want the routing to apply package-wide.
Each failure throws and logs a warning with the exception class in context.exception plus relevant non-PII details (signature byte length, expiry delta, missing-param flags). Successful challenge issuance and signature verification log at info.
| Event | Level | Notable context |
|---|---|---|
| Challenge issued | info |
ttl_seconds |
| Verification succeeded | info |
— |
| Missing param on challenge / verify | warning |
exception, *_empty flags |
| Invalid public key | warning |
exception, input_length |
| Malformed / unknown nonce | warning |
exception, nonce_length, reason |
| Message mismatch | warning |
exception, stored_length, received_length |
| Address mismatch | warning |
exception |
| Challenge expired | warning |
exception, expired_seconds_ago |
| Malformed signature (undecodable or wrong length) | warning |
exception = MalformedSignatureException, signature_byte_length or input_length |
| Invalid signature (verify returned false) | warning |
exception = InvalidSignatureException |
| Nonce race lost | warning |
exception, reason: concurrent_consumption |
No public keys, signatures, or message contents are logged.
Frontend (Phantom wallet)
The wallet connects first so the server knows which address to embed in the SIWS message:
A full Blade example lives in resources/views/auth/solana-login.blade.php.
What the SIWS message looks like
Threat model
What the package defends against, and what it doesn't.
Defends against:
- Forged signatures — Ed25519 verify with the SDK's length-checked binary signature path.
- Replay across sessions (session store) — when using the default
SessionChallengeStore, the challenge is keyed by the Laravel session cookie; an attacker without the victim's session cannot consume their challenge. - Replay of a redeemed nonce — nonce is consumed atomically by the store's
forget(), which returnstrueonly for the caller that actually removed it. Concurrent verifiers on the same valid bundle resolve to exactly one success; the losers getChallengeNotFoundException. - Expired challenges —
Expiration Timeis enforced server-side;ttlconfig has a 60-second floor. - Address swap —
publicKeyin the request must match the address the server embedded in the issued message. - Tampered message — server compares the received message against the stored message with
hash_equalsbefore signature verify. - Malformed nonce — input nonce must match the 32-char alphanumeric format the package issues; bogus values short-circuit to
ChallengeNotFoundException. - Nonce burn on failed verify — failed signature verifies do NOT consume the nonce, so wallet retries and double-submits survive bad input.
Does NOT defend against:
- Wallet-side phishing — if the user signs the message on an attacker's domain, the package on your domain is not involved. Educate users; consider showing the domain field prominently in the wallet prompt.
- Compromised session storage — the challenge lives in the Laravel session. If the session driver is compromised (XSS leaking the cookie, shared session store breach), an attacker can race a valid signature into your endpoint within the TTL window.
- Wallet identity over time — the package authenticates that whoever signed the message controls the wallet right now. It does not say anything about whether the same human still controls that wallet a week later. Treat the wallet address like any other long-lived identity claim and re-authenticate when the action warrants it.
- Bundle interception with the cache store —
CacheChallengeStoreremoves the session-cookie binding, so the 32-character nonce is the only handle. If an attacker can read the full(publicKey, signature, message, nonce)bundle off the wire before the legitimate client redeems it, they can redeem it from any client within the TTL. The signature itself stays valid only against the address embedded in the message — they cannot impersonate a different wallet — but they can race the legitimate caller for the one-time consumption. This is the conventional bearer-token threat model. Use the cache store only on TLS-terminated transports, and prefer the session store whenever a session cookie is available.
Testing
This runs the Pest suite under Orchestra Testbench. Covers the full SIWS round-trip, every exception subclass, signature/message/address tampering, nonce format validation, nonce reuse, expiry, and base58 vs base64 signature decoding.
Contributing
Issues and pull requests welcome at https://github.com/SanderMuller/socialite-solana.
Quality gates on every PR: Pint, PHPStan (level max, strict rules, type-coverage 100%), Rector, full Pest suite.
Credits
License
MIT
All versions of socialite-solana with dependencies
ext-sodium Version *
laravel/framework Version ^11.0|^12.0|^13.0
laravel/socialite Version ^5.0
psr/log Version ^3.0
sandermuller/solana-pubkey Version ^0.1
socialiteproviders/manager Version ^4.9.2