Skip to content
Finowl

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. 1

    Export FINOWL_API_KEY in the environment of your auth service.

  2. 2

    Add breachedCount to the package that handles registration and password resets.

  3. 3

    Call it after your length checks and before generating the bcrypt hash.

  4. 4

    Return a validation error to the client when the count is greater than zero.

  5. 5

    Wrap the call with a metric so you can see timeouts and rejection rates over time.

Check breached passwords in Go before storing them
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

Check breached passwords in Go before storing them

Grab an API key, paste the example and run it. 100 free credits every month.