Password Reset

Implement enumeration-safe reset requests, emailed tokens, and a complete React recovery flow

AuthOS release 0.8.2 TypeScript SDK 0.8.0 Latest-only documentation
Updated Jul 15, 2026 Edit this page
On this page

Use this flow when a password user cannot authenticate. It deliberately returns the same request response whether or not the email address belongs to an account.

Password Reset Workflow

The password reset flow consists of three steps:

  1. User requests a password reset
  2. User receives an email with a reset link
  3. User submits a new password with the reset token

Step 1: Request Password Reset

async function requestPasswordReset(email: string) {
  try {
    const response = await sso.auth.requestPasswordReset({
      email: email,
      org_slug: 'acme-corp' // Optional: use org-specific SMTP
    });

    console.log(response.message);
    // "If the email exists, a password reset link has been sent"
    return true;
  } catch (error) {
    console.error('Failed to send reset email:', error);
    return false;
  }
}

Security Note: The API always returns success, even if the email doesn’t exist. This prevents email enumeration attacks.

Step 2: User Receives Email

The user receives an email with a password reset link in this format:

https://yourdomain.com/reset-password?token=RESET_TOKEN_HERE

You need to implement the /reset-password route in your application to handle this.

Step 3: Reset Password with Token

async function resetPassword(token: string, newPassword: string) {
  try {
    const response = await sso.auth.resetPassword({
      token: token,
      new_password: newPassword
    });

    console.log(response.message);
    // "Password reset successful"
    return true;
  } catch (error) {
    if (error instanceof SsoApiError) {
      if (error.statusCode === 400) {
        console.error('Invalid or expired reset token');
      } else {
        console.error('Password reset failed:', error.message);
      }
    }
    return false;
  }
}

Complete Password Reset Flow

// ForgotPasswordPage.tsx
import { useState } from 'react';
import { SsoClient, SsoApiError } from '@drmhse/sso-sdk';

const sso = new SsoClient({
  baseURL: process.env.REACT_APP_SSO_URL
});

export function ForgotPasswordPage() {
  const [email, setEmail] = useState('');
  const [submitted, setSubmitted] = useState(false);
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);

    try {
      await sso.auth.requestPasswordReset({
        email,
        org_slug: 'acme-corp'
      });
      setSubmitted(true);
    } catch (error) {
      // Even on error, show success to prevent email enumeration
      setSubmitted(true);
    } finally {
      setLoading(false);
    }
  };

  if (submitted) {
    return (
      <div className="success-message">
        <h2>Check Your Email</h2>
        <p>
          If an account exists with <strong>{email}</strong>, you will receive
          a password reset link shortly.
        </p>
        <p>Check your spam folder if you don't see the email.</p>
        <a href="/login">Back to Login</a>
      </div>
    );
  }

  return (
    <form onSubmit={handleSubmit}>
      <h2>Forgot Password</h2>
      <p>Enter your email address and we'll send you a password reset link.</p>

      <div className="form-group">
        <label htmlFor="email">Email Address</label>
        <input
          id="email"
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
          placeholder="you@example.com"
        />
      </div>

      <button type="submit" disabled={loading}>
        {loading ? 'Sending...' : 'Send Reset Link'}
      </button>

      <a href="/login">Back to Login</a>
    </form>
  );
}

// ResetPasswordPage.tsx
import { useState, useEffect } from 'react';
import { SsoClient, SsoApiError } from '@drmhse/sso-sdk';
import { useSearchParams } from 'react-router-dom';

const sso = new SsoClient({
  baseURL: process.env.REACT_APP_SSO_URL
});

export function ResetPasswordPage() {
  const [searchParams] = useSearchParams();
  const [token, setToken] = useState('');
  const [password, setPassword] = useState('');
  const [confirmPassword, setConfirmPassword] = useState('');
  const [error, setError] = useState('');
  const [success, setSuccess] = useState(false);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const resetToken = searchParams.get('token');
    if (resetToken) {
      setToken(resetToken);
    } else {
      setError('Invalid reset link');
    }
  }, [searchParams]);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setError('');

    if (password !== confirmPassword) {
      setError('Passwords do not match');
      return;
    }

    if (password.length < 8) {
      setError('Password must be at least 8 characters');
      return;
    }

    setLoading(true);

    try {
      await sso.auth.resetPassword({
        token,
        new_password: password
      });
      setSuccess(true);
    } catch (err) {
      if (err instanceof SsoApiError) {
        if (err.statusCode === 400) {
          setError('This reset link is invalid or has expired');
        } else {
          setError(err.message);
        }
      } else {
        setError('Password reset failed. Please try again.');
      }
      setLoading(false);
    }
  };

  if (success) {
    return (
      <div className="success-message">
        <h2>Password Reset Complete</h2>
        <p>Your password has been successfully reset.</p>
        <a href="/login">Sign In</a>
      </div>
    );
  }

  return (
    <form onSubmit={handleSubmit}>
      <h2>Reset Password</h2>

      <div className="form-group">
        <label htmlFor="password">New Password</label>
        <input
          id="password"
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          required
          placeholder="At least 8 characters"
        />
      </div>

      <div className="form-group">
        <label htmlFor="confirmPassword">Confirm New Password</label>
        <input
          id="confirmPassword"
          type="password"
          value={confirmPassword}
          onChange={(e) => setConfirmPassword(e.target.value)}
          required
        />
      </div>

      {error && <div className="error">{error}</div>}

      <button type="submit" disabled={loading || !token}>
        {loading ? 'Resetting...' : 'Reset Password'}
      </button>
    </form>
  );
}

Next step

Continue to Account management for password changes made from an authenticated session.