Authsome

Password Authentication

Password-based sign-up, sign-in, and account management with bcrypt or argon2id hashing.

The password plugin provides email/password authentication with a full account lifecycle: sign-up, sign-in, forgot password, reset password, change password, and email verification. It is the most commonly used plugin and is typically the first one added to any Authsome engine.

Setup

import (
    "github.com/xraph/authsome"
    "github.com/xraph/authsome/plugins/password"
)

eng, err := authsome.New(
    authsome.WithStore(store),
    authsome.WithPlugin(password.New()),
    authsome.WithConfig(authsome.Config{
        AppID: "myapp",
        Password: authsome.PasswordConfig{
            MinLength:        10,
            RequireUppercase: true,
            RequireLowercase: true,
            RequireDigit:     true,
            RequireSpecial:   false,
            Algorithm:        "bcrypt",
            BcryptCost:       12,
            CheckBreached:    true,
        },
    }),
)

The plugin constructor accepts optional per-plugin config for domain restrictions:

password.New(password.Config{
    // Only allow signup from these email domains.
    AllowedDomains: []string{"mycompany.com", "partner.com"},
})

Sign-up flow

POST /v1/auth/signup

The signup endpoint creates a user account and returns a session in a single call.

Request:

{
  "email": "alice@example.com",
  "password": "Secure!Pass99",
  "username": "alice",
  "name": "Alice Liddell",
  "app_id": "myapp",
  "metadata": {
    "company": "Acme Corp",
    "plan": "pro"
  }
}

Response:

{
  "user": {
    "id": "ausr_01j9...",
    "email": "alice@example.com",
    "username": "alice",
    "name": "Alice Liddell",
    "email_verified": false,
    "app_id": "aapp_01j9...",
    "metadata": {"company": "Acme Corp", "plan": "pro"},
    "created_at": "2024-11-01T10:00:00Z"
  },
  "session": {
    "id": "ases_01j9...",
    "token": "a3f8c9d4...",
    "refresh_token": "d72b1ef8...",
    "expires_at": "2024-11-01T11:00:00Z",
    "refresh_token_expires_at": "2024-12-01T10:00:00Z"
  }
}

The engine performs these checks before creating the user:

  1. Validates the password against the policy (length, complexity)
  2. Checks HaveIBeenPwned if CheckBreached: true
  3. Validates any custom form fields against the active FormConfig
  4. Verifies email uniqueness within the app
  5. Verifies username uniqueness (if provided)
  6. Hashes the password with the configured algorithm
  7. Calls BeforeSignUp plugin hooks
  8. Creates the user record
  9. Calls BeforeSessionCreate hooks, creates the session
  10. Emits ActionSignUp on the hook bus

Sign-in flow

POST /v1/auth/signin

{
  "email": "alice@example.com",
  "password": "Secure!Pass99",
  "app_id": "myapp"
}

You can sign in with email or username:

{
  "username": "alice",
  "password": "Secure!Pass99",
  "app_id": "myapp"
}

The engine checks in order:

  1. Account lockout status
  2. User existence (returns ErrInvalidCredentials on miss to prevent enumeration)
  3. Ban status
  4. Password verification
  5. Password expiry (MaxAgeDays)
  6. Transparent rehash if algorithm changed

On success, a new session is created and returned.

Password hashing

bcrypt (default)

bcrypt is the default algorithm. The BcryptCost parameter controls the work factor:

Password: authsome.PasswordConfig{
    Algorithm:  "bcrypt",
    BcryptCost: 12, // OWASP minimum recommended
}

Increasing the cost factor makes hashing slower and more resistant to brute-force attacks. Each increment doubles the computation time. A cost of 12 takes approximately 250ms on modern hardware — acceptable for sign-in but not for high-throughput bulk operations.

argon2id

argon2id is the winner of the Password Hashing Competition and is generally preferred for new applications. It is memory-hard, making GPU attacks more expensive.

Password: authsome.PasswordConfig{
    Algorithm: "argon2id",
    Argon2: authsome.Argon2Config{
        Memory:      65536, // 64 MiB
        Iterations:  3,
        Parallelism: 2,
        SaltLength:  16,
        KeyLength:   32,
    },
}

The defaults meet OWASP recommendations.

Transparent algorithm migration

When you change Algorithm from "bcrypt" to "argon2id", existing users are not affected until their next successful sign-in. On sign-in, the engine detects that the stored hash uses a different algorithm and transparently re-hashes the password with the new algorithm:

// Simplified engine logic on sign-in:
if account.NeedsRehash(u.PasswordHash, policy) {
    newHash, _ := account.HashPasswordWithPolicy(req.Password, policy)
    u.PasswordHash = newHash
    store.UpdateUser(ctx, u)
}

No user downtime, no migration scripts. Users are migrated automatically as they sign in.

Password policies

The PasswordConfig is enforced at signup and on every password change operation:

Password: authsome.PasswordConfig{
    MinLength:        12,
    RequireUppercase: true,
    RequireLowercase: true,
    RequireDigit:     true,
    RequireSpecial:   true,
    CheckBreached:    true,
    HistoryCount:     5,    // can't reuse last 5 passwords
    MaxAgeDays:       90,   // password expires after 90 days
}

Breach checking

When CheckBreached: true, the engine queries the HaveIBeenPwned k-anonymity API before storing a password. Only the first 5 characters of the SHA-1 hash are sent — the full password is never transmitted.

The check is fail-open: if the API is unreachable or returns an error, the password is accepted. Use this for consumer-facing applications where security is important but user experience must not be blocked by external API failures.

Password history

When HistoryCount > 0, Authsome stores previous password hashes in the PasswordHistoryStore. To use this feature, configure a password history store:

import "github.com/xraph/authsome/account"

eng, err := authsome.New(
    authsome.WithStore(store),
    authsome.WithPasswordHistory(store), // store must implement account.PasswordHistoryStore
    authsome.WithConfig(authsome.Config{
        Password: authsome.PasswordConfig{
            HistoryCount: 10,
        },
    }),
)

On password change or reset, the engine compares the new password against all stored historical hashes. If any match, ErrPasswordReused is returned.

Password reset flow

The password reset flow is a two-step process:

Request reset: The user submits their email address.

POST /v1/auth/forgot-password

{"email": "alice@example.com", "app_id": "myapp"}

The engine creates a password reset record with a 1-hour expiry token. The response is always 200 OK regardless of whether the email exists — this prevents email enumeration.

The reset record is returned to your code (not sent directly). You are responsible for delivering the token via email using your Mailer bridge or custom logic:

// In your webhook handler or custom code:
pr, err := engine.ForgotPassword(ctx, appID, email)
if pr != nil {
    mailer.Send(email, "Reset your password", resetURL+"?token="+pr.Token)
}

Confirm reset: The user submits the token and new password.

POST /v1/auth/reset-password

{
  "token": "the-reset-token-from-email",
  "new_password": "NewSecure!Pass99"
}

The engine validates the token, enforces the password policy, checks breach status and history, updates the password hash, consumes the token (one-time use), and revokes all existing sessions.

Change password flow

Authenticated users can change their password by providing their current password:

POST /v1/auth/change-password

{
  "current_password": "Secure!Pass99",
  "new_password": "EvenBetter!Pass123"
}

Requires a valid session token in the Authorization: Bearer header. The engine verifies the current password before applying the change. All existing sessions are not automatically revoked — only the user's password record is updated.

API routes

All routes are registered under BasePath (default /v1/auth):

MethodPathDescription
POST/signupCreate user account and return session
POST/signinAuthenticate with email/password
POST/signoutRevoke current session
POST/refreshExchange refresh token for new access token
GET/meReturn authenticated user
PUT/meUpdate authenticated user profile
POST/forgot-passwordRequest password reset token
POST/reset-passwordConfirm reset with token + new password
POST/change-passwordChange password (requires current password)
POST/verify-emailVerify email address with token

On this page