Documentation
¶
Overview ¶
Package auth holds the authentication primitives that were previously only present as generated code.
It is deliberately narrow. #52 proposes extracting the whole authentication system as a framework-agnostic module, which is a larger and riskier piece of work than it looks -- roughly 1,100 lines currently living in templates, plus store interfaces that do not exist yet, plus a security review that the issue itself sets as a precondition for tagging. Cramming that into the tail of a release is how auth libraries ship with the bug they were written to avoid.
What is here instead is the part that is self-contained, has no dependency on a store or a session, and is where two of this project's four advisories actually lived: API token generation and verification. Generated code cannot be unit-tested, which is precisely why those defects reached users. This can.
The remaining flows -- users, sessions, 2FA, password reset -- stay in templates until they get their own change with its own review.
Example ¶
Example shows the two halves of a token's life. It is compiled and run by `go test`, so it cannot drift from the API the way a README snippet can.
// At issue time: show the plaintext to the user, store only the hash.
issued, err := NewToken(24 * time.Hour)
if err != nil {
panic(err)
}
stored := struct {
Hash []byte
Expiry time.Time
}{issued.Hash, issued.Expiry}
// At request time: the plaintext arrives in a header, and the stored hash
// is all that is needed to check it.
presented, err := FromAuthorizationHeader("Bearer " + issued.PlainText)
if err != nil {
panic(err)
}
check := &Token{Hash: stored.Hash, Expiry: stored.Expiry}
fmt.Println(check.Verify(presented, time.Now()) == nil)
// A token that is not ours does not verify.
fmt.Println(check.Verify("AAAAAAAAAAAAAAAAAAAAAAAAAA", time.Now()) == nil)
Output: true false
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidToken = errors.New("auth: invalid token")
ErrInvalidToken is returned when a token is malformed, unknown or expired.
One error for all three, deliberately: distinguishing "no such token" from "expired token" tells an attacker which of their guesses existed.
Functions ¶
func FromAuthorizationHeader ¶
FromAuthorizationHeader extracts a bearer token.
It rejects anything that is not exactly "Bearer <token>" of the right length, rather than accepting a prefix match. A header of "Bearer" with no value, or with extra fields after the token, is a malformed request rather than something to interpret generously.
func HashToken ¶
HashToken returns the stored form of a plaintext token.
SHA-256 rather than bcrypt, and that is not an oversight. A password is low-entropy and chosen by a human, so it needs a slow hash to survive being guessed. A token is 128 bits from crypto/rand, so there is nothing to guess, and a slow hash would only mean every authenticated API request pays for a key-derivation function.
Types ¶
type Token ¶
type Token struct {
// PlainText is populated only by NewToken. It is never read back from
// storage, because storage never has it.
//
// v0.7.0 fixed exactly this: tokens were persisted in plaintext and
// serialised into JSON responses, so a database read or a logged response
// body handed over working credentials.
PlainText string `json:"-"`
// Hash is what gets stored and compared.
Hash []byte `json:"-"`
Expiry time.Time `json:"expiry"`
}
Token is an API token: a plaintext value shown to the user exactly once, and a hash that is all the server retains.