Go · Password breach checks
Check breached passwords in Go before storing them
In a Go auth service the password check belongs right before you call bcrypt or argon2 on the new password. The Finowl endpoint takes the plaintext over HTTPS, hashes it with SHA-1, searches by a 5-character prefix with padding, and returns breached and count. The password is processed transiently and is never logged, stored or returned, so the only place it can leak is your own code.
The function below returns an int64 count and an error, which reads naturally in Go: zero means not found. Error values include status and error codes but never the password, and the request body is marshaled from an anonymous struct so it never sits in a long-lived variable. The demo reads one line from stdin, which keeps the value out of command-line arguments and the process list.
Steps
- 1
Export FINOWL_API_KEY in the environment of your auth service.
- 2
Add breachedCount to the package that handles registration and password resets.
- 3
Call it after your length checks and before generating the bcrypt hash.
- 4
Return a validation error to the client when the count is greater than zero.
- 5
Wrap the call with a metric so you can see timeouts and rejection rates over time.
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
)
var client = &http.Client{Timeout: 5 * time.Second}
// breachedCount reports how often password appears in breach data.
// It never logs the password or includes it in returned errors.
func breachedCount(password string) (int64, error) {
body, _ := json.Marshal(struct {
Password string `json:"password"`
}{password})
for attempt := 0; attempt < 3; attempt++ {
req, _ := http.NewRequest(http.MethodPost, "https://api.finowl.co/v1/breaches/password", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("FINOWL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return 0, fmt.Errorf("password check request failed: %w", err)
}
var out struct {
Breached bool
Count int64
Error struct{ Code string }
}
_ = json.NewDecoder(resp.Body).Decode(&out)
resp.Body.Close()
switch {
case resp.StatusCode == http.StatusOK:
return out.Count, nil
case resp.StatusCode == http.StatusBadRequest:
return 0, fmt.Errorf("password failed validation: %s", out.Error.Code)
case resp.StatusCode == http.StatusUnauthorized:
return 0, fmt.Errorf("unauthorized: check FINOWL_API_KEY")
case resp.StatusCode == http.StatusTooManyRequests && out.Error.Code == "rate_limited":
secs, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
time.Sleep(time.Duration(max(secs, 1)) * time.Second)
default:
return 0, fmt.Errorf("password check failed: %d %s", resp.StatusCode, out.Error.Code)
}
}
return 0, fmt.Errorf("password check still rate limited")
}
func main() {
fmt.Print("Password to check: ")
line, _ := bufio.NewReader(os.Stdin).ReadString('\n')
count, err := breachedCount(strings.TrimRight(line, "\r\n"))
if err != nil {
log.Fatal(err)
}
if count > 0 {
fmt.Printf("Seen %d times in breach data. Choose another password.\n", count)
} else {
fmt.Println("Not found in breach data.")
}
}IN PRODUCTION
Production tips
Check your HTTP middleware and tracing setup. Request dumps from httputil.DumpRequest or verbose OpenTelemetry attributes on the inbound signup request can capture the password before this function runs.
Keep the client timeout short for this endpoint. Five seconds is plenty for a single lookup, and a fast failure lets you apply your fail-open or fail-closed policy promptly.
Zero the password slice or string reference as soon as you have hashed it. Go cannot guarantee memory wiping, but shortening the lifetime reduces exposure in heap dumps.
Each lookup costs 1 credit, with 100 free credits every month. See pricing and error codes.
A FEW GOOD QUESTIONS
Common questions
Related guides
- Check email breaches in Go with net/http
- Predict gender from names in Go with net/http
- Check breached passwords in Python at signup
- Screen breached passwords in Node.js
- Check breached passwords in TypeScript
- Check breached passwords in Java at registration
- Breached password checks for fintech accounts
- Breached password screening for e-commerce accounts
- Breached password checks for SaaS signup and resets
Check breached passwords in Go before storing them
Grab an API key, paste the example and run it. 100 free credits every month.