Enable passwordless authentication with one-time passwords in PocketBase
OTP (One-Time Password) authentication allows users to sign in using a temporary code sent to their email, providing a passwordless authentication experience.
The OTP authentication endpoint performs several security checks:
// From apis/record_auth_with_otp.go:38event.OTP, err = e.App.FindOTPById(form.OTPId)if err != nil { return e.BadRequestError("Invalid or expired OTP", err)}if event.OTP.CollectionRef() != collection.Id { return e.BadRequestError("Invalid or expired OTP", errors.New("the OTP is for a different collection"))}if event.OTP.HasExpired(collection.OTP.DurationTime()) { return e.BadRequestError("Invalid or expired OTP", errors.New("the OTP is expired"))}
Extra rate limiting: OTP validation has an additional rate limit to prevent brute force attacks:
// From apis/record_auth_with_otp.go:57err = checkRateLimit(e, "@pb_otp_"+event.Record.Id, core.RateLimitRule{MaxRequests: 5, Duration: 180})if err != nil { return e.TooManyRequestsError( "Too many attempts, please try again later with a new OTP.", nil)}
Users are limited to 5 OTP validation attempts per 3 minutes per record. After exceeding the limit, they must request a new OTP.
type EmailTemplate struct { Subject string `json:"subject"` Body string `json:"body"`}
Available placeholders:
{APP_NAME} - Your application name
{APP_URL} - Your application URL
{OTP} - The one-time password
{OTP_ID} - The OTP record ID
Example template:
Subject: Your login code for {APP_NAME}Body:Hello,Your one-time login code is: {OTP}This code will expire in 3 minutes.If you didn't request this code, please ignore this email.Thanks,The {APP_NAME} Team
The OTP record is stored in the _otps system collection:
type OTP struct { *Record}// Core methodsfunc (o *OTP) CollectionRef() stringfunc (o *OTP) SetCollectionRef(collectionId string)func (o *OTP) RecordRef() stringfunc (o *OTP) SetRecordRef(recordId string)func (o *OTP) SentTo() stringfunc (o *OTP) SetSentTo(email string)func (o *OTP) ValidatePassword(password string) boolfunc (o *OTP) SetPassword(password string)func (o *OTP) HasExpired(maxElapsed time.Duration) bool
OTP is ideal for applications that want to offer passwordless authentication:
// Request OTPconst { otpId } = await pb.collection('users').requestOTP('user@example.com');// Prompt user for the code they receivedconst code = prompt('Enter the code from your email:');// Authenticateconst authData = await pb.collection('users').authWithOTP(otpId, code);