Skip to content

TheUntraceable/better-auth-opaque

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Better Auth OPAQUE

npm version License: MIT

A Better Auth plugin that implements zero-knowledge password authentication using the OPAQUE protocol.

This plugin allows you to build authentication systems where the server never sees, stores, or handles the user's raw password, providing exceptional security against a full database breach.

Key Features

  • Zero-Knowledge: The server remains completely ignorant of user passwords, fulfilling a core tenet of privacy-first design.
  • Post-Breach Security: User passwords cannot be cracked offline even if an attacker steals your entire database.
  • Built-in User Enumeration Protection: The login and registration flow is designed to be constant-time, preventing attackers from discovering which emails are registered on your service.
  • Seamless Integration: Designed to work flawlessly within the Better Auth ecosystem.
  • Client-Agnostic API: Provides a clear, multi-stage API for any frontend (web, mobile, CLI) to interact with.

How It Works

The OPAQUE protocol is an asymmetric password-authenticated key exchange (aPAKE). Unlike traditional hash-based systems, the authentication process is an interactive, cryptographic handshake.

  1. Registration:

    • The client initiates a registration request with a value derived from the password.
    • The server responds with a cryptographic challenge.
    • The client uses the password and the server's challenge to create a registrationRecord. This record contains the user's credentials, encrypted in a way that only the user can unlock with their password.
    • The server stores this registrationRecord. The server cannot decrypt it.
  2. Login:

    • The client initiates a login request.
    • The server retrieves the stored registrationRecord and uses it to issue a new challenge.
    • Only the client with the correct password can solve the challenge. Upon success, both the client and server derive a shared session key, proving the client's identity without ever exchanging the password itself.

1. Installation

# Using Bun
bun add better-auth-opaque @serenity-kit/opaque

# Using NPM
npm install better-auth-opaque @serenity-kit/opaque

# Using Yarn
yarn add better-auth-opaque @serenity-kit/opaque

2. Setup & Configuration

Step 2.1: Set Environment Variables

You must provide a secret server key for the OPAQUE protocol. This key should be kept private and remain stable for the lifetime of your application.

Generate a secure, 171 character long key:

npx @serenity-kit/opaque@latest create-server-setup

Add the generated key to your .env file:

# .env
OPAQUE_SERVER_KEY="your-secure-randomly-generated-key-here"

Step 2.2: Integrate with Better Auth

Import the opaque plugin and add it to your Better Auth configuration.

// src/lib/auth.ts
import { BetterAuth } from "better-auth";
import { opaque } from "better-auth-opaque";
// ... other imports (adapters, etc.)

export const auth = BetterAuth({
    secret: process.env.AUTH_SECRET, // This is also used to encrypt user data sent to the client for login to persist state
    plugins: [
        // Add the OPAQUE plugin
        opaque({
            OPAQUE_SERVER_KEY: process.env.OPAQUE_SERVER_KEY,
        }),
    ],
});
// src/lib/auth-client.ts
import { createAuthClient } from "better-auth/client";
import { opaqueClient } from "better-auth-opaque";

export const authClient = createAuthClient({
    // Any config you already have,
    plugins: [
        opaqueClient()
        // ... other plugins
    ]
});

3. Client-Side Implementation

Your frontend will need to interact with the multi-stage API endpoints. You will also need the @serenity-kit/opaque library on the client.

Here is a reference implementation using React/TypeScript.

// src/components/AuthForm.tsx
import { useState } from 'react';
import { authClient } from "@/lib/auth-client";

export const AuthForm = () => {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');

    const handleRegister = async () => {
        const { data, error } = await authClient.signUp.opaque({
            email,
            password,
            name: "User's Name",
        })
        // The user is now registered BUT NOT logged in. You must log them in separately.
        // This is for security because registration can be used to enumerate users by seeing already registered emails
        // do not return a session on registration, yet registering new users does.
        
        // if you wish the user to be logged in immediately after registration,
        // you can call the login function here.
    };

    const handleLogin = async () => {
        try {
            const { data, error } = await authClient.signIn.opaque({
                email,
                password
            });
            // The user is now logged in!
        } catch (error) {
            console.error('Login failed:', error);
        }
    };

    return (
        <div>
            {/* ... form inputs for email and password ... */}
            <button onClick={handleRegister}>Register</button>
            <button onClick={handleLogin}>Login</button>
        </div>
    );
};

4. API Endpoints Reference

This plugin adds the following four endpoints to your Better Auth instance:

Flow Method Endpoint Purpose
Register POST /api/auth/sign-up/opaque/challenge Client sends initial request; Server responds with a challenge.
Register POST /api/auth/sign-up/opaque/complete Client sends final record; Server creates user BUT NOT a session.
Login POST /api/auth/sign-in/opaque/challenge Client sends initial request; Server responds with a challenge.
Login POST /api/auth/sign-in/opaque/complete Client sends final proof; Server validates and creates a session.

5. Security Considerations

  • OPAQUE_SERVER_KEY: Your server key must be kept secret and should never be committed to version control. Treat it with the same care as a database password or API secret.
  • User Enumeration: This plugin automatically protects against user enumeration attacks. Requests for non-existent users will receive a cryptographically valid-looking (but ultimately fake) challenge, ensuring that an attacker cannot distinguish between a registered and an unregistered email address by observing server responses.

License

This project is licensed under the MIT License.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors