# How Uptime Kuma Implements 2FA Authentication: A Complete Technical Guide to TOTP

> Uptime Kuma implements 2FA authentication with TOTP via WebSocket. Explore the technical details of its secure token verification and secret storage.

- Repository: [Louis Lam/uptime-kuma](https://github.com/louislam/uptime-kuma)
- Tags: deep-dive
- Published: 2026-02-28

---

**Uptime Kuma implements 2FA authentication using Time-Based One-Time Password (TOTP) through WebSocket events, storing base32-encoded secrets in the user table and verifying tokens with the `notp` library using a 30-second time step and single-window tolerance.**

Uptime Kuma is a widely-used open-source monitoring dashboard that requires robust access controls for production environments. The 2FA authentication system in Uptime Kuma leverages a custom **WebSocket-based architecture** to generate secrets, verify tokens, and manage user authentication flows without external dependencies. This implementation uses cryptographic random generation for secrets and enforces replay protection through database-level token tracking.

## 2FA Architecture and Core Components

The system uses **Time-Based One-Time Password (TOTP)** as defined in RFC 6238, implemented through socket.io events rather than traditional HTTP endpoints. The architecture spans five primary files:

- **[`server/server.js`](https://github.com/louislam/uptime-kuma/blob/main/server/server.js)** — Contains the core socket event handlers (`prepare2FA`, `verifyToken`, `save2FA`, `disable2FA`) and TOTP verification logic
- **[`server/2fa.js`](https://github.com/louislam/uptime-kuma/blob/main/server/2fa.js)** — Provides the `TwoFA.disable2FA()` helper for removing 2FA requirements
- **[`src/util.js`](https://github.com/louislam/uptime-kuma/blob/main/src/util.js)** — Houses the `genSecret()` function for cryptographically random secret generation
- **[`src/components/TwoFADialog.vue`](https://github.com/louislam/uptime-kuma/blob/main/src/components/TwoFADialog.vue)** — Vue component managing the QR code display and token input interface
- **[`src/mixins/socket.js`](https://github.com/louislam/uptime-kuma/blob/main/src/mixins/socket.js)** — Frontend wrapper providing convenient methods like `prepare2FA()` and `verifyToken()`

## Server-Side 2FA Implementation in server/server.js

### Generating Secrets with prepare2FA

The `prepare2FA` socket event creates a new TOTP secret when users initiate 2FA setup. The server calls `genSecret()` from [`src/util.js`](https://github.com/louislam/uptime-kuma/blob/main/src/util.js) (lines 38-45) to generate a random alphanumeric string, encodes it using **base32**, and strips padding characters to create a standards-compliant secret. It stores the raw secret in the `user.twofa_secret` column and returns an `otpauth://` URI for QR code generation.

```javascript
// server/server.js – inside socket.on("prepare2FA")
let newSecret = genSecret();                         // random secret
let encodedSecret = base32.encode(newSecret);        // base32 encode
encodedSecret = encodedSecret.toString().replace(/=/g, ""); // remove padding

let uri = `otpauth://totp/Uptime%20Kuma:${user.username}?secret=${encodedSecret}`;

// Persist secret for the user
await R.exec("UPDATE `user` SET twofa_secret = ? WHERE id = ?", [newSecret, socket.userID]);

callback({ ok: true, uri });

```

### Verifying TOTP Tokens

Token verification occurs in the `verifyToken` event handler using the `notp` library. The system validates tokens against the stored secret with `twoFAVerifyOptions`—configured with a **30-second time step** and a **window of 1**—allowing for slight clock drift. Critical security logic checks `user.twofa_last_token` to prevent replay attacks by rejecting previously used tokens.

```javascript
// server/server.js – inside socket.on("verifyToken")
let verify = notp.totp.verify(token, user.twofa_secret, twoFAVerifyOptions);

if (user.twofa_last_token !== token && verify) {
    callback({ ok: true, valid: true });
} else {
    callback({ ok: false, msg: "authInvalidToken", msgi18n: true, valid: false });
}

```

### Enabling and Disabling 2FA

Once verified, the `save2FA` event sets `user.twofa_status = 1` to enable 2FA for the account. Conversely, `disable2FA` calls `TwoFA.disable2FA(userID)` from [`server/2fa.js`](https://github.com/louislam/uptime-kuma/blob/main/server/2fa.js), which executes an SQL UPDATE to reset `twofa_status` to 0 and clear the secret.

## Database Schema and Security Model

The 2FA state persists in three columns of the `user` table:

- **`twofa_status`** — Integer flag (0 = disabled, 1 = enabled)
- **`twofa_secret`** — Raw TOTP secret used for token generation
- **`twofa_last_token`** — Stores the most recently valid token to prevent replay attacks

This schema ensures that secrets never leave the server after generation, and the `twofa_last_token` field provides atomic protection against token replay attempts.

## Client-Side 2FA Flow in TwoFADialog.vue

The Vue component [`src/components/TwoFADialog.vue`](https://github.com/louislam/uptime-kuma/blob/main/src/components/TwoFADialog.vue) orchestrates the user experience through three stages. First, `prepare2FA()` emits the socket event to retrieve the QR code URI:

```javascript
// src/components/TwoFADialog.vue – method prepare2FA()
prepare2FA() {
    this.processing = true;
    this.$root.getSocket().emit("prepare2FA", this.currentPassword, (res) => {
        this.processing = false;
        if (res.ok) {
            this.uri = res.uri;               // show QR code
        } else {
            this.$root.toastError(res.msg);
        }
    });
}

```

Second, `verifyToken()` validates the user's 6-digit code before enabling:

```javascript
// src/components/TwoFADialog.vue – method verifyToken()
verifyToken() {
    this.$root.getSocket().emit("verifyToken", this.token, (res) => {
        if (res.ok && res.valid) {
            this.tokenValid = true;           // enable Save button
        } else {
            this.$root.toastError(res.msg);
        }
    });
}

```

Finally, `save2FA()` persists the 2FA state after confirmation:

```javascript
// src/components/TwoFADialog.vue – save2FA() called after confirmation
save2FA() {
    this.processing = true;
    this.$root.getSocket().emit("save2FA", this.currentPassword, (res) => {
        this.processing = false;
        if (res.ok) {
            this.getStatus();                // refresh status
        } else {
            this.$root.toastError(res.msg);
        }
    });
}

```

## 2FA Integration in the Login Flow

During authentication, the `login` socket event checks `user.twofa_status` after password verification. If enabled, the server responds with `{ tokenRequired: true }`, prompting the client for a TOTP code. The server then verifies the supplied token using `notp.totp.verify` with the same `twoFAVerifyOptions` used during setup. Successful verification updates `twofa_last_token` and issues the authentication JWT.

## Rate Limiting and Brute Force Protection

All 2FA-related socket events route through `twoFaRateLimiter` defined in [`server/rate-limiter.js`](https://github.com/louislam/uptime-kuma/blob/main/server/rate-limiter.js). This middleware mitigates brute-force attempts against the 6-digit TOTP codes by limiting request frequency from individual IP addresses or user sessions, adding a critical layer of protection against automated attacks.

## Summary

- Uptime Kuma uses **TOTP (Time-Based One-Time Password)** with **WebSocket events** for all 2FA operations
- Secrets are generated via `genSecret()` in [`src/util.js`](https://github.com/louislam/uptime-kuma/blob/main/src/util.js), **base32-encoded**, and stored in the `user.twofa_secret` column
- Token verification uses `notp.totp.verify` with a **30-second step** and **window of 1** to prevent brute force while allowing clock drift
- **Replay protection** is enforced by checking `twofa_last_token` against previously used codes
- The frontend implementation lives in [`src/components/TwoFADialog.vue`](https://github.com/louislam/uptime-kuma/blob/main/src/components/TwoFADialog.vue) with socket wrappers in [`src/mixins/socket.js`](https://github.com/louislam/uptime-kuma/blob/main/src/mixins/socket.js)
- **Rate limiting** via `twoFaRateLimiter` protects all 2FA endpoints from brute-force attacks

## Frequently Asked Questions

### What 2FA method does Uptime Kuma use?

Uptime Kuma implements standard **Time-Based One-Time Password (TOTP)** compatible with authenticator apps like Google Authenticator and Authy. It does not use SMS, email, or hardware keys, but rather generates RFC 6238-compliant TOTP codes through the `notp` library with 30-second validity windows.

### How does Uptime Kuma prevent replay attacks?

The system stores the last valid token in the `user.twofa_last_token` database column. During verification in [`server/server.js`](https://github.com/louislam/uptime-kuma/blob/main/server/server.js), the server explicitly checks that the submitted token differs from this stored value before accepting it, ensuring each 6-digit code can only be used once for authentication.

### What are the TOTP verification settings in Uptime Kuma?

According to the `twoFAVerifyOptions` defined in [`server/server.js`](https://github.com/louislam/uptime-kuma/blob/main/server/server.js), the implementation uses a **30-second time step** with a verification **window of 1**. This allows the server to accept the current token plus one previous or future token, accommodating minor clock differences between the server and the user's device.

### Which files contain the complete 2FA implementation?

The core logic resides in [`server/server.js`](https://github.com/louislam/uptime-kuma/blob/main/server/server.js) (socket events), [`server/2fa.js`](https://github.com/louislam/uptime-kuma/blob/main/server/2fa.js) (disable helper), and [`src/util.js`](https://github.com/louislam/uptime-kuma/blob/main/src/util.js) (secret generation). The user interface is built in [`src/components/TwoFADialog.vue`](https://github.com/louislam/uptime-kuma/blob/main/src/components/TwoFADialog.vue), while [`src/mixins/socket.js`](https://github.com/louislam/uptime-kuma/blob/main/src/mixins/socket.js) provides the frontend socket wrappers. Rate limiting is configured in [`server/rate-limiter.js`](https://github.com/louislam/uptime-kuma/blob/main/server/rate-limiter.js).