authcore

package module
v1.11.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 29, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

README

🛡️ authcore

Auth is the code you only get wrong once. authcore does the dangerous parts for you.

Argon2id passwords · EdDSA tokens · refresh rotation · API keys · social login (OIDC + OAuth2) · email + username validation — all secure by default, timing-safe, zero-config, in pure Go. No database. No framework. No crypto PhD. Apache-2.0.

CI Go Reference

Install · Quick start · Why · Modules · Examples · Docs


// Without authcore — every line is a chance to leak or weaken something:
salt := make([]byte, 16); rand.Read(salt)               // right size? right RNG?
key := argon2.IDKey(pw, salt, 3, 64*1024, 2, 32)        // OWASP params? memorised?
stored := encodePHC(salt, key)                           // hand-rolled format…
if subtle.ConstantTimeCompare(a, b) == 1 { /* login */ } // remembered constant-time?
// …then generate Ed25519 keys, sign a JWT, hash + rotate refresh tokens, repeat.

// With authcore — secure defaults, nothing to get wrong:
hash, _ := pwd.Hash(password)                // Argon2id · salted · PHC-encoded
ok,   _ := pwd.Verify(attempt, hash)         // constant-time, always
pair, _ := tokens.CreateTokens(userID, claims) // EdDSA-signed access + refresh

📦 Install

go get github.com/Glyndor/authcore

Requires Go 1.26+. On first run, Ed25519 keys + an HMAC secret are generated under ./.authcore/ — point KeysDir at a secrets volume in production.

🚀 Quick start

// One-time setup at startup. Keys are created on first run.
auth, _ := authcore.New(authcore.DefaultConfig())

pwd, _    := password.New(auth)                          // Argon2id, OWASP defaults
tokens, _ := jwt.New[UserClaims](auth, jwt.DefaultConfig())

// Register: store only the hash, never the plaintext.
hash, err := pwd.Hash("Str0ng-P@ssword!")                // err == password.ErrWeakPassword tells the user why

// Log in: verify, then mint an access + refresh pair.
if ok, _ := pwd.Verify("Str0ng-P@ssword!", hash); ok {
    pair, _ := tokens.CreateTokens(userID, UserClaims{Role: "admin"})
    // pair.AccessToken      → Authorization: Bearer …
    // pair.RefreshTokenHash → store server-side (never the raw token)
    // pair.SessionID        → UUID v7, use as your session PK
}

[!TIP] Full, runnable versions live in examples/go run ./examples/jwt/. Wiring into a real HTTP stack: Fiber · Gin.

⚡ Why

Roll your own and own every footgun. Run a full identity platform for a login form. Or reach for authcore — the dangerous primitives, done right, in-process.

Roll your own Full IdP (Ory, Keycloak) authcore
Time to first login Hours – days Hours (+ ops) ~5 minutes
Argon2id · EdDSA · timing-safe Manual, easy to slip by default
Automatic key management Manual
Database / HTTP server You build it Theirs (locked in) Bring your own
Extra service to run No Yes No
You own the data model

🔐 Modules

Pick only what you need — each is independent, testable, and safe by default.

Module Does
🔑 password Hash + verify. Argon2id, policy-enforced, self-describing PHC format.
🎫 jwt Access + refresh tokens. EdDSA / Ed25519, generic claims, rotation.
📧 email Validate + normalize. RFC 5321/5322, optional cached DNS MX check.
👤 username Validate + normalize. Reserved-name blocklist, character rules.
🗝️ apikey Opaque API keys. Generate, keyed-hash for storage, constant-time verify.
🌐 oauth Social login — Google, Microsoft (OIDC) and GitHub, Discord (OAuth2). Auth Code + PKCE, ID-token validation or userinfo.
flowchart LR
    App["Your app"] -->|init once| Core["authcore"]
    Core -->|auto-generates| Keys[("🔑 Ed25519 + HMAC<br/>on disk")]
    Core -->|Provider| M["password · jwt · apikey · oauth<br/>email · username"]
    M -->|hash · sign · verify| App

📖 Docs

New here? Start with the Secure login recipe — the step-by-step flow that turns these primitives into a login an auditor accepts.

Secure login recipe · Password · JWT · Email & username · API keys · OIDC login · Key management · Configuration · Testing & modules · Migrating from bcrypt · Errors · FAQ · Versioning

Full API reference on pkg.go.dev.

License

Apache-2.0 — report vulnerabilities privately via the Security tab, never in a public issue.

Documentation

Overview

Package authcore provides a modular, production-ready authentication library for Go.

Quick start

cfg := authcore.DefaultConfig()
auth, err := authcore.New(cfg)
if err != nil {
    log.Fatal(err)
}

Modules

Authentication mechanisms live under the auth/ sub-tree. Each module is an independent package that accepts an authcore.Provider — the narrow interface that *AuthCore satisfies — so modules remain independently testable.

auth/jwt      — JSON Web Tokens (access + refresh, EdDSA)
auth/password — Argon2id password hashing
auth/email    — email validation and normalization
auth/username — username validation and normalization
auth/apikey   — opaque API key generation and validation
auth/oauth    — OpenID Connect / OAuth 2.0 client (social login)

Key management

authcore automatically generates and persists cryptographic keys on first run. Keys are stored in Config.KeysDir (default ".authcore") and are protected by a .gitignore so they are never accidentally committed.

Extending authcore

To write a new module, accept a Provider in your constructor and implement the Module interface:

type MyModule struct { ... }

func New(p authcore.Provider, cfg Config) (*MyModule, error) { ... }

func (m *MyModule) Name() string { return "mymodule" }

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidConfig is returned when the supplied Config fails validation.
	ErrInvalidConfig = errors.New("authcore: invalid configuration")

	// ErrInvalidTimezone is returned when Config.Timezone is nil.
	ErrInvalidTimezone = errors.New("authcore: timezone must not be nil")

	// ErrKeyManager is returned when the key management system fails to
	// initialise, generate, or load cryptographic material.
	ErrKeyManager = errors.New("authcore: key manager failure")
)

Sentinel errors returned by the authcore package. Use errors.Is to check for these in calling code.

Functions

This section is empty.

Types

type AuthCore

type AuthCore struct {
	// contains filtered or unexported fields
}

AuthCore is the central object of the library. It holds shared configuration, the logger, and the key manager, and is the entry point for all authentication sub-modules.

Create one instance per application; it is safe for concurrent use.

func New

func New(cfg Config) (*AuthCore, error)

New creates and returns a fully initialised *AuthCore.

It applies defaults to any zero-value fields in cfg, validates the result, selects or creates a logger, and initialises the key manager.

On first run the key manager creates Config.KeysDir and generates fresh Ed25519 keys and an HMAC refresh secret. On subsequent runs the existing files are loaded and validated.

New returns a wrapped ErrInvalidConfig on bad configuration, or a wrapped ErrKeyManager when key initialisation fails. Both are unwrappable with errors.Is.

// Minimal — all defaults apply.
auth, err := authcore.New(authcore.DefaultConfig())

// Custom keys directory (useful in containers or tests).
cfg := authcore.DefaultConfig()
cfg.KeysDir = "/run/secrets/authcore"
auth, err := authcore.New(cfg)
Example
package main

import (
	"fmt"
	"os"

	"github.com/Glyndor/authcore"
)

func main() {
	dir, _ := os.MkdirTemp("", "authcore-example-")
	defer func() { _ = os.RemoveAll(dir) }()

	cfg := authcore.DefaultConfig()
	cfg.EnableLogs = false
	cfg.KeysDir = dir

	auth, err := authcore.New(cfg)
	if err != nil {
		panic(err)
	}
	fmt.Println(auth.Keys() != nil)
}
Output:
true

func (*AuthCore) Config

func (a *AuthCore) Config() Config

Config returns a copy of the active configuration. Sub-modules should call this to read shared settings.

func (*AuthCore) Keys

func (a *AuthCore) Keys() Keys

Keys returns the library's managed cryptographic material. Sub-modules use this to obtain the Ed25519 signing key and the HMAC refresh secret without needing direct file-system access.

func (*AuthCore) Logger

func (a *AuthCore) Logger() Logger

Logger returns the active Logger. Sub-modules must use this logger rather than creating their own so that all output flows through a single, user-configured sink.

type Config

type Config struct {
	// EnableLogs controls whether the library emits log output.
	// Defaults to true.
	EnableLogs bool

	// Timezone is used for any time-sensitive operations inside the library.
	// Defaults to time.UTC.
	Timezone *time.Location

	// Logger allows callers to inject a custom logging backend
	// (e.g. slog, zap, zerolog). When set, EnableLogs is ignored.
	// If nil and EnableLogs is true, a default stdlib logger is used.
	Logger Logger

	// KeysDir is the directory where authcore creates and stores cryptographic
	// key files (ed25519_private.pem, ed25519_public.pem, refresh_secret.key).
	//
	// Defaults to ".authcore" relative to the current working directory.
	// Use an absolute path in containerised or restricted environments.
	//
	// The directory is created automatically on first use. A .gitignore is
	// written inside it to prevent accidental commits of key material.
	//
	// Ignored when KeyStore is set.
	KeysDir string

	// KeyStore optionally overrides where cryptographic keys come from. When
	// nil (the default), authcore uses the disk store under KeysDir. Set it to
	// source keys from a secret manager, environment, or KMS instead — see
	// NewKeyStoreFromKeys and NewKeyStoreFromPEM. When set, KeysDir is ignored.
	KeyStore KeyStore
}

Config holds the top-level configuration for an AuthCore instance. Zero values are replaced by safe defaults via DefaultConfig or applyDefaults.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a Config populated with safe, production-ready defaults.

cfg := authcore.DefaultConfig()
cfg.EnableLogs = false          // disable logs for tests
auth, err := authcore.New(cfg)
Example
package main

import (
	"fmt"

	"github.com/Glyndor/authcore"
)

func main() {
	cfg := authcore.DefaultConfig()
	fmt.Println(cfg.KeysDir)
}
Output:
.authcore

type KeyStore added in v1.6.0

type KeyStore interface {
	// Load returns the key material, or an error if it cannot be obtained or
	// fails validation.
	Load() (Keys, error)
}

KeyStore sources authcore's cryptographic key material (the Ed25519 signing pair and the HMAC refresh secret).

The default is disk: New generates and loads PEM files under Config.KeysDir. Set Config.KeyStore to override that — for example to source keys from a secret manager, environment variables, or KMS, which is the right fit for serverless or disk-averse deployments where a mounted volume is awkward.

Implementations are consulted once, at New time. The returned Keys must be stable for the lifetime of the AuthCore instance.

func NewKeyStoreFromKeys added in v1.6.0

func NewKeyStoreFromKeys(priv ed25519.PrivateKey, pub ed25519.PublicKey, refreshSecret []byte) (KeyStore, error)

NewKeyStoreFromKeys returns a KeyStore backed by in-memory Ed25519 material, touching no filesystem. Use it to inject keys obtained from a secret manager or KMS at startup.

It validates that pub is the public half of priv and that refreshSecret is 32 bytes. Assign the result to Config.KeyStore.

func NewKeyStoreFromPEM added in v1.6.0

func NewKeyStoreFromPEM(privatePEM, publicPEM, refreshSecret []byte) (KeyStore, error)

NewKeyStoreFromPEM returns a KeyStore from PEM-encoded material: a PKCS#8 private key, a PKIX public key, and the raw 32-byte refresh secret. This is the convenient form when keys arrive as PEM strings from environment variables or a secret store.

Assign the result to Config.KeyStore.

type Keys

type Keys interface {
	// PrivateKey returns the Ed25519 private key used for signing tokens.
	// The caller must not modify the returned slice.
	PrivateKey() ed25519.PrivateKey

	// PublicKey returns the Ed25519 public key used for signature verification.
	// The caller must not modify the returned slice.
	PublicKey() ed25519.PublicKey

	// RefreshSecret returns the 32-byte HMAC-SHA256 key used to hash refresh
	// tokens before they are stored in a database.
	// The caller must not modify the returned slice.
	RefreshSecret() []byte

	// KeyID returns the stable identifier for the current signing key.
	// It is derived from the public key and embedded in the "kid" JOSE header
	// of every issued token so that verifiers can select the correct key when
	// multiple keys are in circulation (e.g. during key rotation).
	KeyID() string
}

Keys provides read-only access to authcore's managed cryptographic material.

The concrete implementation is *keymanager.KeyManager (internal). Sub-modules receive Keys through the Provider interface and must not cache or copy the raw key bytes — always call the accessor methods.

type Logger

type Logger interface {
	Debug(msg string, args ...any)
	Info(msg string, args ...any)
	Warn(msg string, args ...any)
	Error(msg string, args ...any)
}

Logger is the logging interface used throughout authcore. Implement this interface to plug in any logging backend (slog, zap, zerolog, etc.).

type Module

type Module interface {
	// Name returns the unique, lowercase identifier of this module.
	// It must be stable across releases because callers may use it as a key.
	// Examples: "jwt", "apikey", "oauth"
	Name() string
}

Module is the minimal contract every authcore authentication sub-module must implement. It acts as a marker interface today and will grow as shared lifecycle requirements (e.g. Close, HealthCheck) are identified.

Available implementations and their concrete constructors:

auth/jwt      — JSON Web Token authentication (EdDSA / Ed25519)
                  jwt.New[T any](p authcore.Provider, cfg ...jwt.Config) (*jwt.JWT[T], error)
auth/password — Argon2id password hashing
                  password.New(p authcore.Provider, cfg ...password.Config) (*password.Password, error)
auth/email    — email validation, normalization, DNS MX verification
                  email.New(p authcore.Provider) (*email.Email, error)
auth/username — username validation, normalization, reserved name blocklist
                  username.New(p authcore.Provider) (*username.Username, error)
auth/apikey   — opaque API key generation, hashing, and verification
                  apikey.New(p authcore.Provider, cfg ...apikey.Config) (*apikey.APIKey, error)
auth/oauth    — OpenID Connect client (login with Google / Microsoft / OIDC)
                  oauth.New(p authcore.Provider, cfg oauth.Config) (*oauth.Client, error)

Conventions shared by every module:

  1. The first argument is always an authcore.Provider (never a concrete *AuthCore).
  2. Where a module takes a Config, it is variadic-optional: omit it, or pass a zero-value Config, to apply safe production defaults via applyDefaults. The email and username modules need no Config. Construct any module and use it — none requires cleanup.
  3. Constructors return a pointer receiver; concrete types are safe for concurrent use across goroutines after construction completes.

type Provider

type Provider interface {
	// Config returns a copy of the active library configuration.
	Config() Config

	// Logger returns the active logger shared across the library.
	// Modules must use this logger rather than creating their own so that
	// all output flows through a single, user-configured sink.
	Logger() Logger

	// Keys returns the library's managed cryptographic material.
	// Sub-modules must call this to obtain signing keys and the refresh secret.
	Keys() Keys
}

Provider is the narrow interface that *AuthCore satisfies.

Sub-modules must accept a Provider rather than *AuthCore directly. This decouples each module from the concrete library type, which has two important consequences:

  1. Testability — unit tests can inject a stub Provider without constructing a real AuthCore or touching the file system / network.

  2. Stability — if AuthCore gains new methods in a future release, existing module code is unaffected because it only depends on the methods below.

Guaranteed implementation: *AuthCore.

Directories

Path Synopsis
auth
apikey
Package apikey issues and verifies opaque API keys for authcore.
Package apikey issues and verifies opaque API keys for authcore.
email
Package email provides email address validation and normalization for authcore.
Package email provides email address validation and normalization for authcore.
jwt
Package jwt provides JSON Web Token (JWT) authentication for authcore.
Package jwt provides JSON Web Token (JWT) authentication for authcore.
oauth
Package oauth is an OpenID Connect (OIDC) client for authcore — "log in with Google / Microsoft / any OIDC provider".
Package oauth is an OpenID Connect (OIDC) client for authcore — "log in with Google / Microsoft / any OIDC provider".
password
Package password provides Argon2id password hashing for authcore.
Package password provides Argon2id password hashing for authcore.
username
Package username provides username validation and normalization for authcore.
Package username provides username validation and normalization for authcore.
internal
clock
Package clock provides a time-source abstraction used across all authcore modules.
Package clock provides a time-source abstraction used across all authcore modules.
keymanager
Package keymanager handles the creation, storage, and loading of authcore's cryptographic key material.
Package keymanager handles the creation, storage, and loading of authcore's cryptographic key material.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL