Documentation
¶
Overview ¶
Package auth provides a complete, self-hosted user authentication system for Go web applications.
It includes:
- Email/Password authentication
- OAuth2 login (Google, Facebook, Twitter) with built-in providers
- Pluggable OAuth provider API for adding custom providers (e.g., GitHub, GitLab)
- SAML 2.0 support for Enterprise SSO
- User session management via HTTP cookies
- Password reset flows via Email
- Automatic database schema management
Quick Start ¶
This example shows how to set up the authentication server with a SQLite database.
package main
import (
"log"
"net/http"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
"github.com/smhanov/auth"
)
func main() {
// 1. Connect to the database
db, err := sqlx.Open("sqlite3", "users.db")
if err != nil {
log.Fatal(err)
}
// 2. Configure auth settings
settings := auth.DefaultSettings
settings.SMTPServer = "smtp.gmail.com:587"
settings.SMTPUser = "example@gmail.com"
settings.SMTPPassword = "app-password"
settings.EmailFrom = "MyApp <support@myapp.com>"
// 3. Create the handler
// NewUserDB will automatically create necessary tables
authHandler := auth.New(auth.NewUserDB(db), settings)
// 4. Mount the handler
// The endpoints will be available under /user/...
// If your frontend runs on another origin, wrap with auth.CORS
// and explicitly list the trusted origins.
http.Handle("/user/", auth.CORS(authHandler, []string{"https://app.example.com"}))
log.Println("Listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Authentication Primer ¶
If you are unfamiliar with the different authentication methods, here is a guide on when to use what.
1. OAuth 2.0 (Social Login)
OAuth is the industry standard for letting users sign in with their existing accounts (Google, Facebook, Twitter). This reduces friction as users don't need to create a new password for your site.
- How it works: Your app redirects the user to the provider (e.g., Google). The user consents, and Google redirects them back to your site with a token.
- Use case: Consumer-facing applications.
2. SAML 2.0 (Enterprise SSO)
SAML is an XML-based standard used by large enterprises. It allows companies to manage their employees' access to your software centrally. Instead of creating 100 accounts on your site, the company connects their Identity Provider (IdP) like Okta or Active Directory to your app.
- How it works: Your app redirects the employee to their company login page. After successful login, the company sends a signed XML "Assertion" to your app.
- Use case: B2B software selling to large organizations.
OAuth Configuration ¶
The auth package supports OAuth2 login through both built-in providers and custom providers via the OAuthProvider interface.
There are two different redirect concepts in the OAuth flow:
- `*RedirectURL` settings such as `GoogleRedirectURL` define the callback URL that the OAuth provider redirects back to.
- The final browser destination after successful authentication is controlled separately with the `next` query parameter on `/user/oauth/login/{provider}`.
To enable OAuth login with social providers, configure the following settings in the Settings struct:
1. Twitter
- TwitterClientID: Your Twitter OAuth 2.0 Client ID from the Twitter Developer Portal.
- TwitterClientSecret: Your Twitter OAuth 2.0 Client Secret.
- TwitterRedirectURL: Optional override for the callback URL. In most cases, leave this blank to use the default `{scheme}://{server}/user/oauth/callback/twitter`, derived from the HTTP request.
- TwitterUseEmail: Set to true to request the user's email address during authentication. This requires the users.email scope and will fetch the email from the /2/users/me endpoint with user.fields=confirmed_email.
2. Google
- GoogleClientID: Your Google OAuth 2.0 Client ID.
- GoogleClientSecret: Your Google OAuth 2.0 Client Secret.
- GoogleRedirectURL: Optional override for the callback URL. In most cases, leave this blank to use the default `{scheme}://{server}/user/oauth/callback/google`, derived from the HTTP request.
3. Facebook
- FacebookClientID: Your Facebook OAuth 2.0 App ID.
- FacebookClientSecret: Your Facebook OAuth 2.0 App Secret.
- FacebookRedirectURL: Optional override for the callback URL. In most cases, leave this blank to use the default `{scheme}://{server}/user/oauth/callback/facebook`, derived from the HTTP request.
When using this default callback URL behavior behind a proxy, ensure both `X-Forwarded-Proto` and `X-Forwarded-Host` are set correctly.
Custom OAuth Providers ¶
You can add support for any OAuth2 provider by implementing the OAuthProvider interface. The interface requires four methods:
- Name() string: A unique identifier for the provider, used in URL routing and the database.
- OAuthConfig() *oauth2.Config: Returns the OAuth2 configuration (client ID, secret, scopes, endpoint).
- UsePKCE() bool: Whether to use PKCE (Proof Key for Code Exchange) for added security.
- FetchIdentity(ctx, client) (id, email, error): Fetches the user's ID and email from the provider.
The framework handles the full server-side OAuth flow automatically: state generation, CSRF protection via cookies, authorization code exchange, and user sign-in/creation.
Example: Adding a GitHub OAuth provider:
type GitHubProvider struct {
ClientID string
ClientSecret string
RedirectURL string // optional; leave empty for auto-derived URL
}
func (p *GitHubProvider) Name() string { return "github" }
func (p *GitHubProvider) UsePKCE() bool { return false }
func (p *GitHubProvider) OAuthConfig() *oauth2.Config {
return &oauth2.Config{
ClientID: p.ClientID,
ClientSecret: p.ClientSecret,
RedirectURL: p.RedirectURL,
Scopes: []string{"user:email"},
Endpoint: oauth2.Endpoint{
AuthURL: "https://github.com/login/oauth/authorize",
TokenURL: "https://github.com/login/oauth/access_token",
},
}
}
func (p *GitHubProvider) FetchIdentity(ctx context.Context, client *http.Client) (string, string, error) {
resp, err := client.Get("https://api.github.com/user")
if err != nil {
return "", "", err
}
defer resp.Body.Close()
var user struct {
ID int `json:"id"`
Login string `json:"login"`
Email string `json:"email"`
}
json.NewDecoder(resp.Body).Decode(&user)
email := user.Email
if email == "" {
email = fmt.Sprintf("%s@users.noreply.github.com", user.Login)
}
return fmt.Sprintf("%d", user.ID), email, nil
}
Register the provider in Settings:
settings := auth.DefaultSettings
settings.OAuthProviders = []auth.OAuthProvider{
&GitHubProvider{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
},
}
authHandler := auth.New(db, settings)
The login and callback endpoints are automatically registered:
/user/oauth/login/github - Redirects to GitHub authorization page /user/oauth/callback/github - Handles the OAuth callback
Use them in your frontend:
<a href="/user/oauth/login/github?next=/dashboard">Sign in with GitHub</a>
The RedirectURL in OAuthConfig can be:
- Empty: automatically derived as {scheme}://{host}/user/oauth/callback/{name}
- A relative path (e.g., "/callback"): resolved against the request
- An absolute URL: used as-is
The `next` query parameter on the login URL controls where the user is redirected after successful authentication (defaults to "/").
Tutorials & Usage ¶
Authentication Endpoints:
The following endpoints are exposed by the handler:
- POST /user/create - Create a new account (email, password)
- POST /user/auth - Sign in (email, password)
- GET /user/signout - Sign out the current user
- GET /user/get - Get current user info (JSON)
- POST /user/forgotpassword - Request a password reset email
- POST /user/resetpassword - Reset password using token
1. Email and Password Authentication
To sign up a new user, send a POST request:
POST /user/create Form Data: email: user@example.com password: secret-password signin: 1 (Optional: set to 1 to auto-login after creation)
To sign in:
POST /user/auth Form Data: email: user@example.com password: secret-password
The server responds with a JSON object containing user info and sets a `session` cookie.
2. OAuth Interaction (Google, Facebook, Twitter)
To enable OAuth, configure the Client ID and Secret in your Settings. In most cases, leave `*RedirectURL` blank so the callback URL is automatically derived as `{scheme}://{server}/user/oauth/callback/{provider}` from the HTTP request:
settings.GoogleClientID = "YOUR_CLIENT_ID" settings.GoogleClientSecret = "YOUR_CLIENT_SECRET" // settings.GoogleRedirectURL = "" // recommended in most deployments
If you are behind a reverse proxy, ensure `X-Forwarded-Proto` and `X-Forwarded-Host` are set so scheme and host are detected correctly.
Important: `GoogleRedirectURL`, `FacebookRedirectURL`, and `TwitterRedirectURL` are the provider callback URLs, not the page the user sees after login completes.
Start the login flow by sending the user to the login URL.
Redirect Parameters: You can control where the user is sent after a successful login using the `next` query parameter.
<a href="/user/oauth/login/google?next=/dashboard">Login with Google</a>
You can also use a different destination per request, for example:
/user/oauth/login/google?next=/settings /user/oauth/login/twitter?next=/billing
If `next` is omitted, the user is redirected to `/`.
3. SAML Interaction
SAML requires a certificate and private key to sign requests. If columns `certificate` and `privatekey` are missing in the database, the library generates them automatically.
Your Service Provider (SP) Metadata is available at:
GET /user/saml/metadata
Provide this URL (or the XML content) to your customer's IT department to configure their IdP. The Assertion Consumer Service (ACS) URL they will need is:
POST /user/saml/acs
4. Database Integration
The package includes a default `UserDB` based on `sqlx` which supports SQLite and PostgreSQL. It handles user storage, session tracking, and OAuth linkage internally. You can wrap or replace `NewUserDB` if you need custom data storage.
Advanced Customization ¶
1. Customizing User Information with GetInfo
By default, the system returns basic user info (userid, email, settings, and OAuth methods). To customize the information returned to clients, override the GetInfo method.
Example:
type MyDB struct {
*auth.UserDB
}
type CustomUserInfo struct {
UserID int64 `json:"userid"`
Email string `json:"email"`
Name string `json:"name"`
AvatarURL string `json:"avatar_url"`
Premium bool `json:"premium"`
Methods []string `json:"methods"`
NewAccount bool `json:"newAccount"`
}
func (db *MyDB) GetInfo(tx auth.Tx, userid int64, newAccount bool) auth.UserInfo {
// Access the underlying sqlx transaction
utx := tx.(*auth.UserTx)
var info CustomUserInfo
err := utx.Tx.Get(&info, `
SELECT u.userid, u.email, p.name, p.avatar_url, p.premium
FROM users u
LEFT JOIN user_profiles p ON u.userid = p.userid
WHERE u.userid = $1
`, userid)
if err != nil {
panic(err)
}
info.Methods = utx.GetOauthMethods(userid)
info.NewAccount = newAccount
return info
}
// Create the handler with your custom DB
authHandler := auth.New(&MyDB{auth.NewUserDB(db)}, settings)
Important: The GetInfo method is called after successful authentication, account creation, and password resets. It returns the data that will be sent to the client as JSON.
2. Event Hooks with OnAuthEvent
To perform actions when users authenticate (e.g., logging, analytics, welcome emails), use the OnAuthEvent callback:
settings.OnAuthEvent = func(tx auth.Tx, action string, userid int64, info auth.UserInfo) {
switch action {
case "create":
// User just created an account
log.Printf("New user created: %d", userid)
// Send welcome email, create user profile, etc.
case "auth":
// User signed in
log.Printf("User %d signed in", userid)
// Update last login timestamp, analytics, etc.
case "resetpassword":
// User reset their password
log.Printf("User %d reset password", userid)
// Send security notification email
}
// You can access the database within the transaction
utx := tx.(*auth.UserTx)
utx.Tx.Exec(`UPDATE users SET last_login = NOW() WHERE userid = $1`, userid)
}
Note: The callback is executed within the same database transaction as the authentication. If you panic or the transaction fails, the authentication will be rolled back. For async operations (like sending emails), consider using a goroutine.
3. Updating User Information
To update a user's email or password, send a POST request to `/user/update`:
Update Email Only:
POST /user/update Form Data: email: newemail@example.com current_password: current-secret-password Requires: User must be signed in (have valid session cookie)
Update Password Only:
POST /user/update Form Data: password: new-secret-password current_password: current-secret-password Requires: User must be signed in (have valid session cookie)
Update Both:
POST /user/update Form Data: email: newemail@example.com password: new-secret-password current_password: current-secret-password Requires: User must be signed in (have valid session cookie)
If neither email nor password is provided, the request returns a 400 error. If `current_password` is missing or incorrect, the request returns a 401 error. Email addresses are automatically converted to lowercase.
4. Adding OAuth Methods to Existing Accounts
Users can link OAuth providers to their existing accounts:
POST /user/oauth/add Form Data: method: google (or "facebook", "twitter") token: oauth-token (from OAuth flow) update_email: true (Optional: update user's email to OAuth email) Requires: User must be signed in
This is useful when:
- A user initially created an account with email/password and now wants to link Google
- A user wants to link multiple OAuth providers to one account
To remove an OAuth method:
POST /user/oauth/remove Form Data: method: google Requires: User must be signed in
5. SAML Identity Provider Selection
For enterprise applications where different users authenticate with different SAML providers, override GetSamlIdentityProviderForUser:
type MyDB struct {
*auth.UserDB
}
type MyTx struct {
auth.Tx
}
func (db *MyDB) Begin(ctx context.Context) auth.Tx {
return &MyTx{db.UserDB.Begin(ctx)}
}
func (tx *MyTx) GetSamlIdentityProviderForUser(email string) string {
// Route by email domain
if strings.HasSuffix(email, "@company1.com") {
return tx.GetSamlIdentityProviderByID("https://company1.okta.com")
}
if strings.HasSuffix(email, "@company2.com") {
return tx.GetSamlIdentityProviderByID("https://company2-idp.com")
}
// Return empty string for non-SAML users (will use regular auth)
return ""
}
Before using SAML, register the Identity Provider metadata:
// Fetch and register IDP metadata (do this once, not on every startup)
tx := db.Begin(context.Background())
defer tx.Rollback()
idpMetadataXML := fetchFromURL("https://company.okta.com/metadata")
idpID := auth.GetSamlID(idpMetadataXML)
tx.AddSamlIdentityProviderMetadata(idpID, idpMetadataXML)
tx.Commit()
See example_saml_test.go for a complete working example.
Common Pitfalls ¶
1. Session Cookies and HTTPS
Session cookies are automatically set to "Secure" when the request comes over HTTPS (checked via `auth.IsRequestSecure(r)`). If you're behind a reverse proxy (nginx, CloudFlare, etc.), make sure it sets `X-Forwarded-Proto` correctly. If you rely on default OAuth callback URL deduction, also set `X-Forwarded-Host`.
2. Email Case Sensitivity
All email addresses are automatically converted to lowercase before storage and comparison. Don't manually lowercase emails in your client code - the server handles this.
3. OAuth Redirect URLs
In most cases, leave `GoogleRedirectURL`, `FacebookRedirectURL`, and `TwitterRedirectURL` blank. When blank, the library uses `{scheme}://{server}/user/oauth/callback/{provider}`, where scheme and server are automatically deduced from the HTTP request. If your app runs behind a reverse proxy, ensure `X-Forwarded-Proto` and `X-Forwarded-Host` are set correctly so the derived URL matches what you register with each provider. OAuth redirect URLs registered with providers must still match exactly (including protocol and port) the URL your deployment will generate.
4. Password Reset Token Expiry
Password reset tokens expire after a set period (default implementation doesn't specify, but typically 1-24 hours). Tokens are single-use - once used successfully, they're deleted from the database.
5. CORS and Cookies
If your frontend is on a different domain than your auth server, you need to:
- Use the `auth.CORS()` wrapper with an explicit allow-list: `http.Handle("/user/", auth.CORS(authHandler, []string{"https://app.example.com"}))`
- Configure your frontend to send credentials: `fetch(url, {credentials: 'include'})`
- Ensure both domains are over HTTPS in production
Cross-origin safety notes:
- Only list origins you fully trust. `auth.CORS()` rejects requests with an `Origin` header that is not on the allow-list.
- `auth.UnsafeCORS()` reflects any `Origin` and should only be used for tightly controlled environments, temporary development setups, or when you intentionally accept the risk.
- CORS is not a substitute for CSRF protection. Restricting origins helps prevent browser reads from untrusted sites, but you should still treat cross-origin credentialed requests as sensitive.
6. Transaction Management
When calling `auth.SignInUser()` or other database operations directly:
- Always defer `tx.Rollback()` immediately after creating the transaction
- Only call `tx.Commit()` after all operations succeed
- Don't call both Commit and Rollback - Rollback is safe to call even after Commit
Example:
tx := db.Begin(context.Background()) defer tx.Rollback() // Safe to call even if we Commit // ... do work ... info := authHandler.SignInUser(tx, w, userid, false, auth.IsRequestSecure(r)) tx.Commit() auth.SendJSON(w, info)
Example ¶
package main
import (
"log"
"net/http"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
"github.com/smhanov/auth"
)
const gmailUser = "support@awesomepeaches.com"
const gmailPassword = "awernmx32hdkssk2mssxx" // app password from google
func main() {
// configure how to send password reset emails
settings := auth.DefaultSettings
settings.SMTPServer = "smtp.gmail.com:587"
settings.SMTPUser = gmailUser
settings.SMTPPassword = gmailPassword
settings.ForgotPasswordSubject = "Password reset from awesomepeaches.com"
settings.ForgotPasswordBody = "Please go to this url to reset your password:\n\n https://awesomepeaches.com/forgot-password/?token=${TOKEN}"
settings.EmailFrom = "support@awesomepeaches.com"
db, err := sqlx.Open("sqlite3", "mydatabase.db")
if err != nil {
log.Panic(err)
}
http.Handle("/user/", auth.New(auth.NewUserDB(db), settings))
log.Fatal(http.ListenAndServe(":8080", nil))
}
Output:
Example (Saml) ¶
Example_saml shows how to register Saml providers and override the GetSamlIdentityProviderForUser method
package main
import (
"context"
"io"
"log"
"net/http"
"strings"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
"github.com/smhanov/auth"
)
// Create our own database, and
// override the GetSamlIdentityProviderForUser method
type myDB struct{ auth.DB }
type myTx struct{ auth.Tx }
func (db myDB) Begin(ctx context.Context) auth.Tx {
return myTx{db.DB.Begin(ctx)}
}
func (tx myTx) GetSamlIdentityProviderForUser(email string) string {
if email == "user@example.com" {
return ""
}
return tx.GetSamlIdentityProviderByID("https://samltest.id/saml/idp")
}
func serveMainPage(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(mainWebPage))
}
// Example_saml shows how to register Saml providers and
// override the GetSamlIdentityProviderForUser method
func main() {
// Open the database
rawdb, err := sqlx.Open("sqlite3", "mydatabase.db")
if err != nil {
log.Panic(err)
}
db := myDB{auth.NewUserDB(rawdb)}
// Download IDP metadata and register it. This only needs to be done once, not
// every time the program starts. But for simplicity, we do it here.
xml := fetchURL("https://samltest.id/saml/idp")
tx := db.Begin(context.Background())
tx.AddSamlIdentityProviderMetadata(auth.GetSamlID(xml), xml)
if tx.GetUserByEmail("user@example.com") == 0 {
tx.CreatePasswordUser("user@example.com", auth.HashPassword("password"))
}
tx.Commit()
// Register the handler.
http.Handle("/user/", auth.New(db, auth.DefaultSettings))
http.HandleFunc("/", serveMainPage)
log.Fatal(http.ListenAndServe(":8080", nil))
}
// Fetch the URL and return its contents as a string
func fetchURL(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}
defer resp.Body.Close()
buf := new(strings.Builder)
io.Copy(buf, resp.Body)
return buf.String()
}
const mainWebPage = `<!DOCTYPE html>
<html>
<body>
<div>
To run this,
<ol>
<li>Download <a href="/user/saml/metadata">the Service Provider Metadata</a> and
send it to <a href="https://samltest.id/upload.php">https://samltest.id/upload.php</a>
</ol>
</div>
<div class="wait">Please wait...</div>
<div class="not-signed-in">
<h1>You need to sign in.</h1>
<div>Please sign in as user@example.com with password "password", or enter any other email
to use single sign in.</div>
Email: <input type="text" id="email"><br>
Password: <input type="text" id="password"><br>
<button onclick="signin(false)">Sign in</button>
<button onclick="signin(true)">Start Single Sign-On</button>
</div>
<div class="signed-in">
<h1>Hello, <span class="username"></span></h1>
<pre class="info"></pre>
<button onclick="signout()">Sign out</button>
</div>
<script>
async function main() {
// hide everything until we know if we are signed in.
show(".not-signed-in", false);
show(".signed-in", false);
// get the user information
let response = await fetch("/user/get");
show(".wait", false);
if (response.status === 401) {
// We are not logged in.
show(".not-signed-in", true);
return;
}
onsignedin(await response.json());
}
async function onsignedin(json) {
show(".signed-in", true);
show(".not-signed-in", false);
document.querySelector(".username").textContent = json.email;
document.querySelector(".info").textContent = JSON.stringify(json, null, 4);
}
async function signin(sso) {
let email = document.querySelector("#email").value;
let password = document.querySelector("#password").value;
if (!sso) {
let response = await fetch("/user/auth?email="+encodeURIComponent(email) + "&password="+encodeURIComponent(password));
if (response.status === 200) {
onsignedin(await response.json());
return;
} else if (response.status !== 407) {
alert("Error signing in: " + response.status);
}
}
// for SSO signin, reload the web page
location.href = "/user/auth?sso=1&email="+encodeURIComponent(email);
}
async function signout() {
await fetch("/user/signout");
location.reload();
}
function show(selector, show) {
document.querySelector(selector).style.display = show ? "" : "none";
}
main();
</script>
</body>
</html>`
Output:
Index ¶
- Variables
- func AdvanceTime(amount time.Duration)
- func CORS(fn http.Handler, allowedOrigins []string) http.HandlerFunc
- func CheckUserID(tx Tx, r *http.Request) int64
- func CheckUserIDAndEmail(tx Tx, r *http.Request) (int64, string)
- func CompareHashedPassword(hashedPassword, candidatePassword string) error
- func DoRateLimit(operation string, req *http.Request, user string, rate float64, ...) bool
- func GetHost(request *http.Request) string
- func GetIPAddress(request *http.Request) string
- func GetSamlID(xml string) string
- func GetUserID(tx Tx, r *http.Request) int64
- func GetUserIDAndEmail(tx Tx, r *http.Request) (int64, string)
- func HasRateLimit(name string) bool
- func HashPassword(password string) string
- func IsRequestSecure(r *http.Request) bool
- func MakeCookie() string
- func New(db DB, settings Settings) http.Handler
- func RateLimitAllows(name string, cost, rate float64, period time.Duration) bool
- func RateLimitCheck(name string, cost, rate float64, period time.Duration) bool
- func RecoverErrors(fn http.Handler) http.HandlerFunc
- func SendError(w http.ResponseWriter, status int, err error)
- func SendJSON(w http.ResponseWriter, thing interface{})
- func UnsafeCORS(fn http.Handler) http.HandlerFunc
- func VerifyOauth(method, token string) (string, string)
- type DB
- type FacebookProvider
- type GoogleProvider
- type HTTPError
- type Handler
- type OAuthProvider
- type Settings
- type TwitterProvider
- type Tx
- type UserDB
- type UserInfo
- type UserTx
- func (tx UserTx) AddOauthUser(method string, foreignID string, userid int64)
- func (tx UserTx) AddSamlIdentityProviderMetadata(id, xml string)
- func (tx UserTx) Commit()
- func (tx UserTx) CreatePasswordResetToken(userid int64, token string, expiry int64)
- func (tx UserTx) CreatePasswordUser(email string, password string) int64
- func (tx UserTx) DeleteSessions(userid int64)
- func (tx UserTx) GetEmail(userid int64) string
- func (tx UserTx) GetID(cookie string) int64
- func (tx UserTx) GetInfo(userid int64, newAccount bool) UserInfo
- func (tx UserTx) GetOauthMethods(userid int64) []string
- func (tx UserTx) GetOauthUser(method string, foreignID string) int64
- func (tx UserTx) GetPassword(email string) (int64, string)
- func (tx UserTx) GetSamlIdentityProviderByID(id string) string
- func (tx UserTx) GetSamlIdentityProviderForUser(email string) string
- func (tx UserTx) GetUserByEmail(email string) int64
- func (tx UserTx) GetUserByPasswordResetToken(token string) int64
- func (tx UserTx) GetValue(key string) string
- func (tx UserTx) RemoveOauthMethod(userid int64, method string)
- func (tx UserTx) Rollback()
- func (tx UserTx) SetValue(key, value string)
- func (tx UserTx) SignIn(userid int64, cookie string)
- func (tx UserTx) SignOut(userid int64, cookie string)
- func (tx UserTx) UpdateEmail(userid int64, email string)
- func (tx UserTx) UpdatePassword(userid int64, password string)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var DefaultSettings = Settings{
ForgotPasswordSubject: `Password reset`,
ForgotPasswordBody: `To reset your password, go to ${URL}`,
}
DefaultSettings provide some reasonable defaults
var ErrorDuplicateUser = errors.New("duplicate user")
ErrorDuplicateUser indicates that a user cannot be created because the email already exists. It should be used instead of the cryptic user database constraint validation error.
ErrorUnauthorized is used when the user is not signed in, but is required to be for the operation.
var TestURL string
TestURL if set, will be used instead of an oauth provider like facebook to make requests.
Functions ¶
func AdvanceTime ¶
AdvanceTime is used during testing to simulate time passing
func CORS ¶
func CORS(fn http.Handler, allowedOrigins []string) http.HandlerFunc
CORS wraps an HTTP request handler, adding CORS headers only for the explicitly allowed origins.
Origins must be fully qualified values such as "https://app.example.com". If you need the previous permissive behavior that reflects any Origin, use UnsafeCORS instead.
func CheckUserID ¶
CheckUserID returns the userid if the user is signed in, or 0
func CheckUserIDAndEmail ¶
CheckUserIDAndEmail returns the userid and email if the user is signed in, or 0 and ""
func CompareHashedPassword ¶
CompareHashedPassword compares the hashed password with the one the user entered (unhashed). It returns no error if the passwords match. The default implementation uses bcrypt.CompareHashAndPassword
func DoRateLimit ¶
func DoRateLimit(operation string, req *http.Request, user string, rate float64, period time.Duration) bool
DoRateLimit will rate limit an operation on both the user and ip address. If it is not allowed, it will return false If it is allowed, it will then assume the operation was carried out and return true
func GetHost ¶
GetHost returns the host of the request, taking into account x-forwarded-host headers. May include the port as well.
func GetIPAddress ¶
GetRequestIP returns the Ip address of the request, taking into account x-forwarded-for headers.
func GetSamlID ¶
GetSamlID returns the entity ID contained within the XML for the given identity provider.
func GetUserID ¶
GetUserID returns the userid. It panics with an HttpError if the user is not signed in.
func GetUserIDAndEmail ¶
GetUserIDAndEmail returns the userid and email. It panics with an HttpError if the user is not signed in.
func HasRateLimit ¶
HasRateLimit returns true if the bucket for the given operation has any attempts, regardless of wether they have reached the limit or not.
func HashPassword ¶
HashPassword computes the salted, hashed password using bcypt. Panics on error.
func IsRequestSecure ¶
IsRequestSecure returns true if the request used the HTTPS protocol. It also checks for appropriate Forwarding headers.
func MakeCookie ¶
func MakeCookie() string
MakeCookie generates a long random string suitable for use as a session cookie
func RateLimitAllows ¶
RateLimitAllows will return true if the given operation is allowed. Name is an arbitrary string that uniquely identifies the user and operation. cost is the cost of the operation, and rate is the max cost allowed in the given time period.
Example: user 123 logs in, and the maximum attempts allowed are 5 in a 10 minute period.
if auth.RateLimitAllows(name, 1, 5, 10 * time.Minute) {
// succeeded
}
func RateLimitCheck ¶
RateLimitCheck checks if the given operation would be allowed, but does not update it.
func RecoverErrors ¶
func RecoverErrors(fn http.Handler) http.HandlerFunc
RecoverErrors will wrap an HTTP handler. When a panic occurs, it will print the stack to the log. Secondly, it will return the error text in both the response header and response body so callers can read it reliably.
func SendError ¶
func SendError(w http.ResponseWriter, status int, err error)
SendError writes an error as a status to the output You don't need to use this but it's handy to have!
func SendJSON ¶
func SendJSON(w http.ResponseWriter, thing interface{})
SendJSON will write a json response and set the appropriate content-type header. You don't need to use this but it's handy to have!
func UnsafeCORS ¶
func UnsafeCORS(fn http.Handler) http.HandlerFunc
UnsafeCORS wraps an HTTP request handler and reflects any Origin.
This matches the package's previous CORS behavior and should only be used when you intentionally want to trust every calling origin.
func VerifyOauth ¶
VerifyOauth contacts the oauth provider, specified with method, and retrieves the foriegn user id and foreign email of the user from the token. Returns the foriegn id and email, which can then be used to sign in the user. Valid methods are: "facebook", "google"
Types ¶
type DB ¶
type DB interface {
Begin(ctx context.Context) Tx
// GetInfo optionally allows customizing the user info returned
GetInfo(tx Tx, userid int64, newAccount bool) UserInfo
}
DB is all the operations needed from the database. You can use the built-in userdb provided by this package and override one or more operations.
Any errors should be expressed through panic.
type FacebookProvider ¶
FacebookProvider implements OAuthProvider for Facebook login.
func (*FacebookProvider) FetchIdentity ¶
func (*FacebookProvider) Name ¶
func (p *FacebookProvider) Name() string
func (*FacebookProvider) OAuthConfig ¶
func (p *FacebookProvider) OAuthConfig() *oauth2.Config
func (*FacebookProvider) UsePKCE ¶
func (p *FacebookProvider) UsePKCE() bool
type GoogleProvider ¶
GoogleProvider implements OAuthProvider for Google login.
func (*GoogleProvider) FetchIdentity ¶
func (*GoogleProvider) Name ¶
func (p *GoogleProvider) Name() string
func (*GoogleProvider) OAuthConfig ¶
func (p *GoogleProvider) OAuthConfig() *oauth2.Config
func (*GoogleProvider) UsePKCE ¶
func (p *GoogleProvider) UsePKCE() bool
type HTTPError ¶
HTTPError is an error that should be communicated to the user through an http status code.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler is an HTTP Handler that will perform user authentication and management.
func (*Handler) SignInUser ¶
func (a *Handler) SignInUser(tx Tx, w http.ResponseWriter, userid int64, newAccount bool, secure bool) UserInfo
SignInUser performs the final steps of signing in an authenticated user, including creating a session. It returns the info structure that should be sent. You should first commit the transaction and then send this structure, perhaps using the SendJSON helper.
Secure should be set to true if the http request was sent over HTTPs, to restrict usage of the cookie to https only.
Example: info := auth.SignInUser(tx, w, userid, false, auth.IsRequestSecure(r)) tx.Commit() auth.SendJSON(w, info)
type OAuthProvider ¶
type OAuthProvider interface {
// Name returns the unique provider name (e.g., "github", "google").
// This is used for URL routing (/user/oauth/login/{name} and
// /user/oauth/callback/{name}) and for storing OAuth linkage in the database.
Name() string
// OAuthConfig returns the OAuth2 configuration for this provider.
// The RedirectURL field may be:
// - Empty: defaults to /user/oauth/callback/{name}, resolved against the request
// - A relative path (e.g., "/callback"): resolved against the request host/scheme
// - An absolute URL: used as-is
OAuthConfig() *oauth2.Config
// UsePKCE returns whether this provider should use PKCE (Proof Key for Code Exchange).
// PKCE adds an extra layer of security to the authorization code flow.
// Most modern providers support PKCE; set to true unless the provider doesn't support it.
UsePKCE() bool
// FetchIdentity uses the authenticated OAuth2 client (which already has a valid token)
// to retrieve the user's provider-specific ID and email address.
// The id should be stable and unique for each user on the provider.
// The email is used to match or create local user accounts.
// If the provider doesn't return an email, generate a placeholder
// (e.g., "{id}@provider.example.com").
FetchIdentity(ctx context.Context, client *http.Client) (id string, email string, err error)
}
OAuthProvider defines the interface for pluggable OAuth providers. Implement this interface to add support for additional OAuth providers beyond the built-in Google, Facebook, and Twitter providers.
Each provider handles the server-side OAuth2 authorization code flow:
- Login: redirects the user to the provider's authorization page
- Callback: exchanges the authorization code for a token and fetches user identity
Example implementation for GitHub:
type GitHubProvider struct {
ClientID string
ClientSecret string
RedirectURL string
}
func (p *GitHubProvider) Name() string { return "github" }
func (p *GitHubProvider) UsePKCE() bool { return false }
func (p *GitHubProvider) OAuthConfig() *oauth2.Config {
return &oauth2.Config{
ClientID: p.ClientID,
ClientSecret: p.ClientSecret,
RedirectURL: p.RedirectURL,
Scopes: []string{"user:email"},
Endpoint: oauth2.Endpoint{
AuthURL: "https://github.com/login/oauth/authorize",
TokenURL: "https://github.com/login/oauth/access_token",
},
}
}
func (p *GitHubProvider) FetchIdentity(ctx context.Context, client *http.Client) (string, string, error) {
resp, err := client.Get("https://api.github.com/user")
// ... parse response to extract id and email ...
return id, email, nil
}
type Settings ¶
type Settings struct {
// SMTP Server and port
SMTPServer string
SMTPUser string
SMTPPassword string
// Eg. "My web site <example@example.com>"
EmailFrom string
ForgotPasswordSubject string
// Forgot password email body. This should have ${TOKEN} in it
// which will contain the actual text of the secret token.
ForgotPasswordBody string
// Alternatively, you can use this to send email
SendEmailFn func(email string, url string)
// OnAuthEvent is called when a user signs in or authenticates.
// The action parameter will be one of "auth", "create", or "resetpassword".
OnAuthEvent func(tx Tx, action string, userid int64, info UserInfo)
// Optionally override password hash from bcrypt default. You may override HashPassword,
// or both. If you override HashPassword but not CompareHashedPassword, then
// a CompareHashPasswordFn will be created based on HashPasswordFn.
HashPasswordFn func(password string) string
CompareHashedPasswordFn func(hashedRealPassword, candidatePassword string) error
// Context used during initialization
DefaultContext context.Context
// Twitter Settings
TwitterClientID string
TwitterClientSecret string
TwitterRedirectURL string
TwitterUseEmail bool
// Google Settings
GoogleClientID string
GoogleClientSecret string
GoogleRedirectURL string
// Facebook Settings
FacebookClientID string
FacebookClientSecret string
FacebookRedirectURL string
// OAuthProviders allows registering custom OAuth providers.
// Built-in providers (Google, Facebook, Twitter) are automatically
// registered when their Client ID is configured in Settings.
// Use this field to add additional providers (e.g., GitHub, GitLab).
// See the OAuthProvider interface for implementation details.
OAuthProviders []OAuthProvider
}
Settings is the settings for the auth package
type TwitterProvider ¶
type TwitterProvider struct {
ClientID string
ClientSecret string
RedirectURL string
UseEmail bool
}
TwitterProvider implements OAuthProvider for Twitter (X) login.
func (*TwitterProvider) FetchIdentity ¶
func (*TwitterProvider) Name ¶
func (p *TwitterProvider) Name() string
func (*TwitterProvider) OAuthConfig ¶
func (p *TwitterProvider) OAuthConfig() *oauth2.Config
func (*TwitterProvider) UsePKCE ¶
func (p *TwitterProvider) UsePKCE() bool
type Tx ¶
type Tx interface {
Commit()
Rollback()
AddOauthUser(method string, foreignid string, userid int64)
CreatePasswordUser(email string, password string) int64
CreatePasswordResetToken(userid int64, token string, expiry int64)
GetID(cookie string) int64
GetEmail(userid int64) string
GetInfo(userid int64, newAccount bool) UserInfo
GetOauthMethods(userid int64) []string
GetOauthUser(method string, foreignid string) int64
GetPassword(email string) (int64, string)
GetUserByEmail(email string) int64
GetUserByPasswordResetToken(token string) int64
RemoveOauthMethod(userid int64, method string)
SignIn(userid int64, cookie string)
SignOut(userid int64, cookie string)
DeleteSessions(userid int64)
UpdateEmail(userid int64, email string)
UpdatePassword(userid int64, password string)
// Extra methods added to support SAML
GetValue(key string) string
SetValue(key, value string)
GetSamlIdentityProviderForUser(email string) string
GetSamlIdentityProviderByID(id string) string
AddSamlIdentityProviderMetadata(id string, xml string)
}
Tx is a database transaction that has methods for user authentication. Any error should be communicated by panic()
type UserDB ¶
type UserDB struct {
// contains filtered or unexported fields
}
UserDB is a database that handles user authentication
type UserInfo ¶
type UserInfo interface{}
UserInfo contains whatever information you need about the user for your application. It is returned to the javascript code for successful authentication requests.
type UserTx ¶
UserTx wraps a database transaction
func (UserTx) AddOauthUser ¶
AddOauthUser marks the given OAUTH identify as belonging to this user.
func (UserTx) AddSamlIdentityProviderMetadata ¶
AddSamlIdentityProviderMetadata adds the meta data for the given identity provider to the database. The id should be the one returned by GetSamlID(xml)
func (UserTx) Commit ¶
func (tx UserTx) Commit()
Commit commits a DB transaction with retry logic for database lock errors.
func (UserTx) CreatePasswordResetToken ¶
CreatePasswordResetToken creates the password reset token with the given expiry date in seconds
func (UserTx) CreatePasswordUser ¶
CreatePasswordUser creates a user with the given email and password The email is already in lower case and the password is already hashed.
func (UserTx) DeleteSessions ¶
DeleteSessions invalidates all sessions for the given user.
func (UserTx) GetID ¶
GetID returns the userid associated with the cookie value, or 0 if no user is signed in with that cookie.
func (UserTx) GetInfo ¶
GetInfo by default returns a structure containing the user's userid, email, and settings.
func (UserTx) GetOauthMethods ¶
GetOauthMethods returns the oauth methods associated with the given user
func (UserTx) GetOauthUser ¶
GetOauthUser returns the userid assocaited with the given foreign identity, or 0 if none exists.
func (UserTx) GetPassword ¶
GetPassword searches for the salted hashed password for the given email address. The email is assumed to be already in all lower case. It also returns the userid. If not found, userid will be 0
func (UserTx) GetSamlIdentityProviderByID ¶
GetSamlIdentityProviderByID will return the XML Metadata file for the given identity provider, which has previously been added with AddSamlIdentityProviderMetadata
func (UserTx) GetSamlIdentityProviderForUser ¶
GetSamlIdentityProviderForUser returns the SAML provider metadata for a given user. The choice of which provider to use for the email address is entirely contained in this method. You will have to override the DB interface to implement this in your app, maybe distinguishing based on their email domain. If this method returns the empty string, normal authentication is done. Otherwise, the browser is redirected to the identity provider's sign in page.
func (UserTx) GetUserByEmail ¶
GetUserByEmail finds the userid associated with the email, or returns 0 if none exists.
func (UserTx) GetUserByPasswordResetToken ¶
GetUserByPasswordResetToken finds the given userid from the token if not expired. If not found, return 0. If found, then remove all tokens from that user.
func (UserTx) GetValue ¶
GetValue should look up the given value. If not present return the empty string.
func (UserTx) RemoveOauthMethod ¶
RemoveOauthMethod removes the given method from the user's account
func (UserTx) UpdateEmail ¶
UpdateEmail changes the given user's email. Email must be in lower case already.
func (UserTx) UpdatePassword ¶
UpdatePassword changes the given user's password. Password must be already hashed.