v0.5.0 Release Notes¶
Release Date: 2026-04-05
Highlights¶
- Audience-Aware JWT Tokens: Separate BFF (web browser) and API (programmatic) clients with distinct audience claims for strict path separation
- Ent-Backed Stores: Production-ready database storage for API keys and BFF sessions using Ent ORM
- Token Encryption: AES-256-GCM encryption for tokens at rest in BFF session storage
- Reusable Ent Mixins: Drop-in schema mixins for API keys and BFF sessions
Added¶
Audience-Aware JWT Tokens (session/jwt)¶
New methods for generating audience-scoped tokens:
GenerateBFFTokenPair(): Create token pairs for web clients withaud: "bff"GenerateAPIToken(): Create scoped tokens for API clients withaud: "api"GenerateAccessTokenWithAudience(): Create tokens with custom audiencesValidateAccessTokenWithAudience(): Validate tokens with audience checking
New Claims helpers:
Audience(): Get first audience valueHasAudience(aud): Check for specific audienceWithAudience(...): Set audience (builder pattern)
import "github.com/grokify/coreforge/session/jwt"
// Generate BFF token pair for web clients
pair, err := svc.GenerateBFFTokenPair(userID, email, name)
// Generate API token for programmatic access
token, err := svc.GenerateAPIToken(userID, email, name, []string{"read:users"})
// Validate with audience checking
claims, err := svc.ValidateAccessTokenWithAudience(token, "api")
Token Encryption (session/bff)¶
AES-256-GCM encryption for tokens at rest:
import "github.com/grokify/coreforge/session/bff"
// Create encryptor with 32-byte key
encryptor, err := bff.NewEncryptor([]byte("your-32-byte-encryption-key-here"))
// Encrypt/decrypt tokens
encrypted, err := encryptor.EncryptString(accessToken)
decrypted, err := encryptor.DecryptString(encrypted)
Ent-Backed BFF Session Store (session/bff)¶
Production database storage for BFF sessions:
- Automatic token encryption before storage
- Configurable expired session cleanup interval
EntClientInterfacefor wrapping your Ent client
import "github.com/grokify/coreforge/session/bff"
store, err := bff.NewEntStore(bff.EntStoreConfig{
Client: yourEntClientWrapper,
Encryptor: encryptor,
CleanupInterval: 1 * time.Hour,
})
Ent-Backed API Key Store (identity/apikey)¶
Production database storage for API keys:
import "github.com/grokify/coreforge/identity/apikey"
store, err := apikey.NewEntStore(apikey.EntStoreConfig{
Client: yourEntClientWrapper,
})
svc := apikey.NewService(apikey.ServiceConfig{
Store: store,
})
Ent Schema Mixins (identity/ent/mixin)¶
Reusable mixins for your Ent schemas:
APIKey Mixin fields:
id,name,prefix,key_hash(sensitive)owner_id,organization_id,scopes,descriptionenvironment(live/test),expires_at,last_used_at,last_used_iprevoked,revoked_at,revoked_reason,metadata, timestamps- Indexes:
owner_id,organization_id,prefix,key_hash(unique),environment
BFFSession Mixin fields:
id,user_id,organization_idaccess_token_encrypted,refresh_token_encrypted(sensitive)dpop_key_pair_encrypted,dpop_thumbprintip_address,user_agent,metadatalast_accessed_at,expires_at, timestamps- Indexes:
user_id,expires_at,organization_id
// your-app/ent/schema/api_key.go
package schema
import (
"entgo.io/ent"
cfmixin "github.com/grokify/coreforge/identity/ent/mixin"
)
type APIKey struct {
ent.Schema
}
func (APIKey) Mixin() []ent.Mixin {
return []ent.Mixin{
cfmixin.APIKeyMixin{},
}
}
Documentation¶
- JWT audience separation guide (
docs/bff/audience.md) - Ent-backed session store integration guide (
docs/bff/ent-store.md) - API key Ent store implementation documentation (
docs/identity/api-keys.md) - OAuth client package documentation (
docs/identity/oauthclient.md) - Design documents for JWT audience and Ent stores features
Dependencies¶
github.com/authzed/spicedb: 1.50.0 → 1.51.0entgo.io/ent: 0.14.5 → 0.14.6github.com/danielgtaylor/huma/v2: 2.37.2 → 2.37.3github.com/mattn/go-sqlite3: 1.14.37 → 1.14.38google.golang.org/grpc: 1.79.3 → 1.80.0
Migration Guide¶
Using Audience-Aware Tokens¶
- Update your BFF to use
GenerateBFFTokenPair():
// Before
pair, err := svc.GenerateTokenPair(userID, email, name)
// After - for web clients
pair, err := svc.GenerateBFFTokenPair(userID, email, name)
- Update your API middleware to validate audience:
claims, err := svc.ValidateAccessTokenWithAudience(token, "api")
if err != nil {
// Token invalid or wrong audience
}
Using Ent-Backed Stores¶
- Add the mixin to your Ent schema:
- Implement
EntClientInterfacewrapping your Ent client:
type EntClientWrapper struct {
client *ent.Client
}
func (w *EntClientWrapper) CreateAPIKey(ctx context.Context, key *apikey.APIKey, keyHash string) error {
_, err := w.client.APIKey.Create().
SetID(key.ID).
SetName(key.Name).
SetPrefix(key.Prefix).
SetKeyHash(keyHash).
// ... set other fields
Save(ctx)
return err
}
// Implement other methods...
- Create the store:
store, err := apikey.NewEntStore(apikey.EntStoreConfig{
Client: &EntClientWrapper{client: entClient},
})
Setting Up Token Encryption¶
- Generate a 32-byte encryption key (store securely):
- Create the encryptor and Ent store:
encryptor, err := bff.NewEncryptor([]byte(os.Getenv("SESSION_ENCRYPTION_KEY")))
if err != nil {
log.Fatal(err)
}
store, err := bff.NewEntStore(bff.EntStoreConfig{
Client: yourEntClientWrapper,
Encryptor: encryptor,
})
Semver Note¶
This is a minor version bump (v0.4.0 → v0.5.0) as it adds new features without breaking changes. All existing APIs remain backward compatible.