Skip to content

Authentication Guide

Bifrost supports multiple authentication modes.

Default mode - allows all connections.

auth:
mode: none

Username/password authentication with bcrypt hashes.

auth:
mode: native
native:
users:
- username: admin
password_hash: "$2a$10$..."
groups:
- admins
email: admin@example.com
full_name: "Admin User"
- username: user1
password_hash: "$2a$10$..."
disabled: false

Using the bcrypt tool:

Terminal window
# Using htpasswd
htpasswd -bnBC 10 "" password | tr -d ':\n'
# Using Python
python3 -c "import bcrypt; print(bcrypt.hashpw(b'password', bcrypt.gensalt()).decode())"
# Using Go
go run -mod=mod github.com/rennerdo30/bifrost-proxy/tools/hashpw password

Authenticate against an LDAP directory.

auth:
mode: ldap
ldap:
url: "ldap://ldap.example.com:389"
base_dn: "dc=example,dc=com"
bind_dn: "cn=service,dc=example,dc=com"
bind_password: "${LDAP_BIND_PASSWORD}"
user_filter: "(uid=%s)"
group_filter: "(memberUid=%s)"
require_group: "proxy-users"
tls: false
insecure_skip_verify: false
FieldDescription
urlLDAP server URL
base_dnBase DN for searches
bind_dnDN for service account
bind_passwordService account password
user_filterFilter to find users (%s = username)
group_filterFilter to find user groups
require_groupOnly allow users in this group

Authenticate against the operating system’s user database (PAM on Linux, Directory Services on macOS).

Warning: Platform Support System authentication is only supported on Linux and macOS. Windows is not currently supported. If you need authentication on Windows, use native, ldap, or oauth mode instead.

auth:
mode: system
system:
service: "login" # PAM service name (Linux only)
allowed_users: # Optional: restrict to specific users
- alice
- bob
allowed_groups: # Optional: restrict to specific groups
- admin
- staff
PlatformSupportMethod
Linux✅ SupportedPAM via su command
macOS✅ SupportedDirectory Services (dscl)
Windows❌ Not SupportedUse native/ldap/oauth instead
FieldDescription
servicePAM service name (default: login) - Linux only
allowed_usersOptional list of allowed usernames
allowed_groupsOptional list of allowed groups (user must be in at least one)
  • Linux: Requires that the Bifrost process can execute su (typically needs to run as root or with appropriate privileges)
  • macOS: Requires access to Directory Services via dscl

Authenticate using OAuth 2.0 or OpenID Connect.

auth:
mode: oauth
oauth:
provider: "generic"
client_id: "${OAUTH_CLIENT_ID}"
client_secret: "${OAUTH_CLIENT_SECRET}"
issuer_url: "https://auth.example.com"
redirect_url: "http://localhost:7081/callback"
scopes:
- openid
- profile
- email

Clients authenticate using Proxy-Authorization header:

Terminal window
curl -x http://user:pass@localhost:7080 http://example.com
Terminal window
curl --socks5 user:pass@localhost:7180 http://example.com

The client can authenticate with the server:

server:
address: "proxy.example.com:7080"
username: "myuser"
password: "mypass"

Authenticate using API keys passed in headers.

auth:
mode: apikey
apikey:
header: "X-API-Key" # Header name to check
keys:
- name: "service-a"
key_hash: "$2a$10$..." # bcrypt hash of the API key
groups: ["api-access"]
- name: "service-b"
key_hash: "$2a$10$..."
groups: ["api-access", "admin"]
Terminal window
# Generate a random API key
openssl rand -base64 32
# Hash the key for storage
python3 -c "import bcrypt; print(bcrypt.hashpw(b'your-api-key', bcrypt.gensalt()).decode())"

Validate JWT tokens with JWKS support for key rotation.

auth:
mode: jwt
jwt:
issuer: "https://auth.example.com"
audience: "bifrost-proxy"
jwks_url: "https://auth.example.com/.well-known/jwks.json"
# Or use static key:
# signing_key: "${JWT_SIGNING_KEY}"
username_claim: "sub"
groups_claim: "groups"
allowed_algorithms:
- RS256
- ES256
FieldDescription
issuerExpected issuer (iss claim)
audienceExpected audience (aud claim)
jwks_urlURL to fetch JSON Web Key Set
signing_keyStatic signing key (alternative to JWKS)
username_claimClaim to use as username
groups_claimClaim containing user groups

Time-based One-Time Password authentication, compatible with Google Authenticator.

auth:
mode: totp
totp:
issuer: "Bifrost Proxy"
digits: 6
period: 30
algorithm: "SHA1"
secrets:
user1: "JBSWY3DPEHPK3PXP" # Base32-encoded secret
user2: "GEZDGNBVGY3TQOJQ"
  1. Generate a secret for each user
  2. Share the secret with the user via QR code or manual entry
  3. User scans with authenticator app (Google Authenticator, Authy, etc.)
Terminal window
# Generate a random TOTP secret
python3 -c "import secrets; import base64; print(base64.b32encode(secrets.token_bytes(20)).decode())"

Counter-based One-Time Password authentication, compatible with YubiKey and similar hardware tokens.

auth:
mode: hotp
hotp:
digits: 6
algorithm: "SHA1"
secrets:
user1:
secret: "JBSWY3DPEHPK3PXP"
counter: 0
user2:
secret: "GEZDGNBVGY3TQOJQ"
counter: 100
look_ahead: 10 # Accept codes within this window

Client certificate authentication for mutual TLS.

auth:
mode: mtls
mtls:
ca_cert: "/path/to/ca.crt"
# Optional: require specific certificate attributes
require_cn: true
allowed_cns:
- "client1.example.com"
- "*.internal.example.com"
# Map certificate subject to user
username_from: "cn" # cn, email, or custom OID
  • Client must present a valid certificate signed by the configured CA
  • Certificate must not be expired or revoked
  • Subject CN or email is used as username

Enterprise SSO using Kerberos with SPNEGO (HTTP Negotiate).

auth:
mode: kerberos
kerberos:
keytab: "/etc/krb5.keytab"
service_principal: "HTTP/proxy.example.com@EXAMPLE.COM"
realm: "EXAMPLE.COM"
# Optional: allowed principals
allowed_principals:
- "*@EXAMPLE.COM"
  1. Create a service principal for the proxy
  2. Export the keytab file
  3. Configure the client to use Kerberos (kinit)
Terminal window
# Create service principal (on KDC)
kadmin -q "addprinc -randkey HTTP/proxy.example.com"
kadmin -q "ktadd -k /etc/krb5.keytab HTTP/proxy.example.com"

Windows domain authentication fallback.

auth:
mode: ntlm
ntlm:
domain: "EXAMPLE"
# Use local SAM database or domain controller
use_domain_controller: true
domain_controller: "dc.example.com"

Warning: Security Note NTLM is considered legacy. Prefer Kerberos when possible.

Combine a primary authentication method with an OTP provider for two-factor authentication.

auth:
mode: mfa_wrapper
mfa_wrapper:
# Primary authentication (username/password)
primary:
mode: native
native:
users:
- username: admin
password_hash: "$2a$10$..."
- username: user1
password_hash: "$2a$10$..."
# Secondary authentication (OTP)
secondary:
mode: totp
totp:
issuer: "Bifrost"
secrets:
admin: "JBSWY3DPEHPK3PXP"
user1: "GEZDGNBVGY3TQOJQ"
# How to submit OTP
otp_header: "X-OTP" # Or append to password with separator
otp_separator: ":" # password:123456
  1. Client sends username and password
  2. If OTP header is present, validate both
  3. If OTP is appended to password (password:123456), split and validate
  4. Both factors must pass for authentication to succeed

Sessions can be stored in memory or Redis for persistence across restarts.

session:
store: redis # or "memory"
redis:
address: "localhost:6379"
password: "${REDIS_PASSWORD}"
db: 0
key_prefix: "bifrost:session:"
ttl: "24h"
cookie_name: "bifrost_session"
cookie_secure: true
cookie_http_only: true
StorePersistenceScalingUse Case
memoryNoSingle instanceDevelopment, simple deployments
redisYesMulti-instanceProduction, HA deployments

The authentication system uses a plugin architecture. Custom authentication providers can be registered:

import "github.com/rennerdo30/bifrost-proxy/internal/auth"
func init() {
auth.RegisterPlugin("custom", &CustomAuthPlugin{})
}
type CustomAuthPlugin struct{}
func (p *CustomAuthPlugin) Name() string { return "custom" }
func (p *CustomAuthPlugin) Init(config map[string]interface{}) error {
// Initialize plugin
return nil
}
func (p *CustomAuthPlugin) Authenticate(ctx context.Context, creds auth.Credentials) (*auth.User, error) {
// Validate credentials
return &auth.User{Username: creds.Username}, nil
}
func (p *CustomAuthPlugin) Close() error {
return nil
}