# New Device Notifications

You’ve probably received an email like this before:

New device notifications alert users when someone logs in from an unrecognized device. This helps users spot unauthorized access immediately and take action if needed.

## How It Works

Device registration uses cryptographic keys to securely identify each device. When a user logs in from a new device, PropelAuth BYO automatically detects it and returns a `newDeviceDetected` flag. You can then notify the user about the new device login.

We provide a frontend library with a function `fetchWithDevice` that is a drop-in replacement for `fetch`, except it automatically handles device registration for you.

The process is simple:

1. Initialize `fetchWithDevice` once when your app loads
2. Use `fetchWithDevice` instead of regular `fetch` for authenticated requests
3. Check the `newDeviceDetected` flag when creating sessions

PropelAuth BYO handles all the cryptographic complexity behind the scenes - generating keys, managing challenges, and verifying signatures.

### Why Cryptographic Keys Over Fingerprinting?

Browser fingerprinting collects device attributes (user agent, screen resolution, installed fonts, etc.) to create a unique identifier. While it can work, it has significant drawbacks:

- **Privacy concerns** - Users and regulations like GDPR increasingly view fingerprinting as invasive
- **False positives** - Browser updates or privacy tools can change fingerprints unexpectedly
- **Limited security** - Fingerprints can be spoofed or copied

Our cryptographic approach is privacy-friendly, reliable, and provides stronger security. Plus, the same keys enable [session theft protection](https://docs.byo.propelauth.com/sessions/features/theft-protection) for even better security.

## Getting Started

1. ### [Backend] Create a Device Challenge endpoint

Your frontend will need to fetch device challenges in order to prove its identity. Create a backend endpoint that uses the [Create Device Challenge Command](https://docs.byo.propelauth.com/sessions/reference#create-device-challenge) to generate a challenge.

```javascript
app.get("/api/device-challenge", async (req: Request, res: Response) => {
    const response = await auth.session.device.createChallenge({
        ipAddress: req.socket.remoteAddress,
        userAgent: req.headers["user-agent"],
    });

if (response.ok) {
        res.status(200).json({
            deviceChallenge: response.data.deviceChallenge,
            expiresAt: response.data.expiresAt,
        });
    } else {
        console.error("Error creating device challenge:", response.error);
        return res.status(500).json({ error: "Failed to create device challenge" });
    }
});
```

2. ### [Frontend] Install BYO Library

PropelAuth BYO comes with a JavaScript library to make adding device registration simple.

```bash
npm install @propelauth/byo-javascript
```

3. ### [Frontend] Initialize BYO Library

Initialize the library using the `initFetchWithDevice` function. You’ll include three options:

- `fetchChallenge`: A function that fetches a device challenge from your backend.
- `getChallengeFromFailedResponse`: A function that extracts a device challenge from a failed response (we’ll add this later).
- `fallbackBehavior`: What to do if the browser doesn’t support the required features. You can choose to either “fail” (throw an error) or “fallback” (continue without device registration).

```javascript
import { initFetchWithDevice } from "@propelauth/byo-javascript";

initFetchWithDevice({
    fetchChallenge: async () => {
        const response = await fetch("/api/device-challenge");
        const jsonResponse = await response.json();
        return {
            deviceChallenge: jsonResponse.deviceChallenge,
            expiresAt: new Date(jsonResponse.expiresAt * 1000),
        };
    },
    getChallengeFromFailedResponse: async (response) => {
        return null;
    },
    fallbackBehavior: "fail",
});
```

4. ### [Frontend] Update Fetch to Include Device Challenge

Update your login fetch request(s) to use the `fetchWithDevice` function. This will automatically include the required headers for device registration in the request.

```javascript
import { fetchWithDevice } from "@propelauth/byo-javascript";

const response = await fetchWithDevice("/api/login", {
    method: "POST",
    body: JSON.stringify({ username, password }),
});
```

5. ### [Backend] Add Device Registration to Session Creation

`fetchWithDevice` will automatically include a new header `dpop` in the request. You’ll want to grab that and add it to your [Create Session](https://docs.byo.propelauth.com/sessions/reference#create-session) call. This will register the device and return if the device has not been used previously by the user.

```javascript
const getSignedDeviceChallenge = (req: Request): string | undefined => {
    const dpopHeader = req.headers["dpop"];
    if (!dpopHeader || typeof dpopHeader !== "string") {
        return undefined;
    }
    return dpopHeader;
};

app.post("/api/login", async (req: Request, res: Response) => {
    const signedDeviceChallenge = getSignedDeviceChallenge(req);
    if (!signedDeviceChallenge) {
        return res.status(401).json({ error: "Device challenge required" });
    }
    const result = await client.session.create({
        userId: user.id,
        ipAddress: req.socket.remoteAddress,
        userAgent: req.headers["user-agent"],
        deviceRegistration: {
            signedDeviceChallenge,
            rememberDevice: true,
        },
    });
    if (result.ok) {
        if (result.data.newDeviceDetected) {
            // send new device notification email
        }
        // Save session token in cookie and return success
    } else {
        res.status(500).json({ error: "Failed to create session" });
    }
});
```

6. ### [Both] Handle Device Challenge Error

While rare, it’s possible that the device challenge could be invalid when you go to log the user in. This could happen, for example, if the user’s IP address changed between fetching the challenge and logging in. To account for this, you can check for the `NewDeviceChallengeRequired` error type, which will include a new device challenge that you can return to the frontend:

```javascript
if (result.ok) {
    if (result.data.newDeviceDetected) {
        // send new device notification email
    }
} else if (result.error.type === "NewDeviceChallengeRequired") {
    res.status(425).json({
        errorType: "NewDeviceChallengeRequired",
        deviceChallenge: result.error.details.deviceChallenge,
        expiresAt: result.error.details.expiresAt,
    });
} else {
    res.status(500).json({ error: "Failed to create session" });
}
```

7. ### [Backend] Add Session Theft Protection or Skip It

Now that you’ve added device registration to your login flow, BYO will expect all [Validate Session](https://docs.byo.propelauth.com/sessions/reference#validate-session) calls to include device registration proof. This means you have two options:
- Update all your authenticated requests to use `fetchWithDevice` instead of `fetch`. See [Session Theft Protection](https://docs.byo.propelauth.com/sessions/features/theft-protection) for more details.
