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:
- Validates the password against the policy (length, complexity)
- Checks HaveIBeenPwned if
CheckBreached: true - Validates any custom form fields against the active
FormConfig - Verifies email uniqueness within the app
- Verifies username uniqueness (if provided)
- Hashes the password with the configured algorithm
- Calls
BeforeSignUpplugin hooks - Creates the user record
- Calls
BeforeSessionCreatehooks, creates the session - Emits
ActionSignUpon 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:
- Account lockout status
- User existence (returns
ErrInvalidCredentialson miss to prevent enumeration) - Ban status
- Password verification
- Password expiry (
MaxAgeDays) - 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):
| Method | Path | Description |
|---|---|---|
POST | /signup | Create user account and return session |
POST | /signin | Authenticate with email/password |
POST | /signout | Revoke current session |
POST | /refresh | Exchange refresh token for new access token |
GET | /me | Return authenticated user |
PUT | /me | Update authenticated user profile |
POST | /forgot-password | Request password reset token |
POST | /reset-password | Confirm reset with token + new password |
POST | /change-password | Change password (requires current password) |
POST | /verify-email | Verify email address with token |