Session Theft Protection | PropelAuth BYO Documentation
Session Theft Protection
Session theft occurs when an attacker steals a user’s session cookies, typically through phishing, malware, or network attacks. Once they have a user’s session token, they can impersonate the user and access their account.
PropelAuth BYO’s session theft protection uses cryptographic device verification to detect and block these attacks. Even if an attacker steals your session cookies, they can’t use them without the cryptographic keys stored on your device.
Prerequisites
Before implementing session theft protection, you need to have completed the setup for New Device Notifications. That guide covers:
- Installing the BYO JavaScript library
- Creating a device challenge endpoint
- Initializing
fetchWithDevice - Registering devices during login
If you haven’t set that up yet, start there first.
How It Works
Session theft protection extends device registration by verifying the device on every authenticated request, not just at login. This means:
- When a user logs in, their device is registered (covered in New Device Notifications)
- On every subsequent request, the device must prove its identity
- If an attacker steals the session cookie and tries to use it from a different device, the request is blocked
Implementation
Since you’ve already set up device registration for new device notifications, you only need to:
Update All Authenticated Fetches
Replace all your authenticated fetch calls with fetchWithDevice. This automatically includes the device verification headers.
// Before
const response = await fetch("/api/user-data", {
method: "GET",
credentials: "include",
});
// After
import { fetchWithDevice } from "@propelauth/byo-javascript";
const response = await fetchWithDevice("/api/user-data", {
method: "GET",
credentials: "include",
});
```
2. ### Add Device Verification to Session Validation
Update your backend session validation to verify the device. The `deviceVerification` parameter requires the signed device challenge from the `dpop` header.
const getSignedDeviceChallenge = (req: Request): string | undefined => {
const dpopHeader = req.headers["dpop"];
if (!dpopHeader || typeof dpopHeader !== "string") {
return undefined;
}
return dpopHeader;
};
app.get("/api/user-data", async (req: Request, res: Response) => { const sessionToken = req.cookies.sessionToken; const signedDeviceChallenge = getSignedDeviceChallenge(req);
if (!signedDeviceChallenge) { return res.status(401).json({ error: "Device verification required" }); }
const result = await client.session.validate({ sessionToken: sessionToken, ipAddress: req.socket.remoteAddress, userAgent: req.headers["user-agent"], deviceVerification: { signedDeviceChallenge, }, });
if (result.ok) { // Return user data res.json({ user: result.data.user }); } else if (result.error.type === "NewDeviceChallengeRequired") { // Return new challenge just like in login res.status(425).json({ errorType: "NewDeviceChallengeRequired", deviceChallenge: result.error.details.deviceChallenge, expiresAt: result.error.details.expiresAt, }); } else { // Session invalid or device verification failed res.status(401).json({ error: result.error }); } }); ```
Testing
To verify session theft protection is working:
- Log in from one browser and save the session cookie
- Try using that cookie from a different browser or incognito window
- The request should be rejected with a
DeviceVerificationFailederror