JWT authentication
JWT auth uses signed bearer tokens instead of cookie sessions — a good fit for mobile apps, third-party API clients, and stateless services. Covara provides a server-side JWT adapter and a client JWTClient + useJWTAuth hook with pluggable token storage.
Server
Use the jwtSession session strategy so the middleware validates Authorization: Bearer <token> and populates the request user. It's decoupled from how the user logs in — so the same credential providers (login, signup, social, …) issue JWTs instead of cookies just by swapping the strategy.
import { jwtSession, useAuth } from "covara";
const { router, middleware } = useAuth({
session: jwtSession({
secret: env.JWT_SECRET, // HMAC, or a key pair for RS256/ES256
getUserById: async (id) => db.query.users.findFirst({ where: eq(users.id, id) }),
refreshStore: kvStore, // optional: revocable refresh tokens
}),
login: { validateCredentials: async (email, password) => { /* ... */ } },
signup: { createUser: async (data) => { /* ... */ } },
});
/login and /signup return { accessToken, expiresIn, tokenType: "Bearer" } and set an httpOnly refresh cookie (instead of a session cookie); POST /api/auth/refresh mints a fresh access token. Protected routes read the bearer token.
Migrating from
createJWTAdapter: the standalonecreateJWTAdapter(mounted via its owngetRoutes()+middleware) still works but is deprecated in favor ofjwtSession, which integrates withuseAuth(and its social/MFA/magic-link providers). Its options map 1:1.
Client
With jwtSession, the standard useAuth() hook handles JWTs uniformly — login/signup capture the returned access token and send it as a bearer on subsequent requests (and client.session.login does the same outside React):
import { useAuth } from "covara/client/react";
function SignIn() {
const { login, signup, signInWith } = useAuth<User>();
// login(email, password) -> stores the JWT and authenticates; same code as a cookie session
}
For finer control (manual token storage, refresh scheduling, React Native), use the dedicated useJWTAuth hook / JWTClient:
useJWTAuth
import { initJWTClient, useJWTAuth } from "covara/client/react";
initJWTClient({ baseUrl: location.origin, authPath: "/api/auth" });
function App() {
const { user, accessToken, isAuthenticated, login, signup, logout, refresh } = useJWTAuth<User>();
const onLogin = () => login("user@example.com", "password");
const onSignup = () => signup("user@example.com", "password", "Jane Doe");
if (!isAuthenticated) return <button onClick={onLogin}>Sign in</button>;
return <button onClick={logout}>Sign out {user?.name}</button>;
}
Alternatively configure JWT on the main client and let the generic useAuth hook auto-detect the strategy:
import { getOrCreateClient } from "covara/client";
const client = getOrCreateClient({ baseUrl: location.origin, jwt: { authPath: "/api/auth" } });
const { user, isAuthenticated, accessToken } = useAuth<User>(); // auto-detects JWT
The client refreshes the access token automatically before expiry and retries a request once after a 401 by refreshing. See Client auth.
Token storage
The client stores tokens through a pluggable TokenStorage, so the same code runs in the browser and React Native:
| Storage | Behavior |
|---|---|
MemoryStorage | Most secure; tokens lost on refresh. |
LocalStorageAdapter("prefix_") | Persists across tabs/sessions. |
SessionStorageAdapter("prefix_") | Persists until the tab closes. |
| AsyncStorage-compatible | Provide your own for React Native. |
See React Native for native token storage.
Setting a token manually
client.setAuthToken("your-jwt-token");
client.clearAuthToken();