AuthKnight Integration

AuthKnight provides passwordless authentication via a hosted login page. The user enters their email on AuthKnight's site, receives a verification code, and upon success AuthKnight redirects back to your application with a one-time token. Your server verifies the token with AuthKnight's API and receives the verified email.

How It Works

  1. User clicks "Login with AuthKnight" on your site
  2. Browser redirects to https://authknight.com/app/login with back_url (cancel) and next_url (callback)
  3. User authenticates on AuthKnight (email + verification code)
  4. AuthKnight redirects to your next_url with a once token
  5. Your server verifies the once token with AuthKnight's API
  6. If the user exists, they're logged in. If not, they're redirected to the register page with a temporary key containing the verified email.

Step 1: Implement Required Functions

// User lookup by email (required)
func userFindByEmail(ctx context.Context, email string, options types.UserAuthOptions) (string, error) {
    user, err := db.Query("SELECT id FROM users WHERE email = ?", email)
    if err != nil {
        return "", err
    }
    if user == nil {
        return "", fmt.Errorf("user with email %s not found", email)
    }
    return user.ID, nil
}

// User registration (required) — email is already verified by AuthKnight
func userRegister(ctx context.Context, email string, firstName string, lastName string, options types.UserAuthOptions) (string, error) {
    result, err := db.Exec("INSERT INTO users (email, first_name, last_name) VALUES (?, ?, ?)",
        email, firstName, lastName)
    if err != nil {
        return "", err
    }
    id, _ := result.LastInsertId()
    return fmt.Sprintf("%d", id), nil
}

// Token storage (same as other flows)
func userStoreAuthToken(ctx context.Context, token string, userID string, options types.UserAuthOptions) error {
    return sessionStore.Set("auth_token_"+token, userID, 2*time.Hour)
}

func userFindByAuthToken(ctx context.Context, token string, options types.UserAuthOptions) (string, error) {
    return sessionStore.Get("auth_token_" + token)
}

func userLogout(ctx context.Context, userID string, options types.UserAuthOptions) error {
    return sessionStore.Delete("auth_token_" + userID)
}

// Temporary key storage (for storing verified email during registration)
func tempKeySet(key string, value string, expiresSeconds int) error {
    return cacheStore.Set(key, value, time.Duration(expiresSeconds)*time.Second)
}

func tempKeyGet(key string) (string, error) {
    return cacheStore.Get(key)
}

Step 2: Configure Authentication

authInstance, err := auth.NewAuthKnightAuth(types.ConfigAuthKnight{
    ConfigShared: types.ConfigShared{
        Endpoint:                "/auth",
        UrlRedirectOnSuccess:    "/dashboard",
        UseCookies:              true,
        EnableRegistration:      true,
        FuncUserFindByAuthToken: userFindByAuthToken,
        FuncUserLogout:          userLogout,
        FuncUserStoreAuthToken:  userStoreAuthToken,
        FuncTemporaryKeyGet:     tempKeyGet,
        FuncTemporaryKeySet:     tempKeySet,
        CookieConfig: &types.CookieConfig{
            Secure:   types.CookieInsecure, // use CookieSecure in production
            HttpOnly: types.CookieHttpOnly,
            SameSite: http.SameSiteLaxMode,
            Path:     "/",
            MaxAge:   2 * 60 * 60,
        },
    },

    // AuthKnight-specific
    FuncUserFindByEmail: userFindByEmail,
    FuncUserRegister:    userRegister,

    // Optional: custom redirect URL after auth (defaults to UrlRedirectOnSuccess)
    // FuncRedirectURL: func(ctx context.Context, userID string) string {
    //     return "/dashboard?welcome=1"
    // },

    // Optional: timeout for AuthKnight API calls (defaults to 10 seconds)
    // HTTPTimeout: 10 * time.Second,
})

ConfigAuthKnight Fields

FieldTypeRequiredDescription
ConfigSharedtypes.ConfigSharedYesShared config (endpoint, cookies, callbacks)
FuncUserFindByEmailfunc(ctx, email, options) (string, error)YesFind user by email
FuncUserRegisterfunc(ctx, email, firstName, lastName, options) (string, error)YesRegister new user (email already verified)
FuncRedirectURLfunc(ctx, userID) stringNoCustom redirect URL after auth. Falls back to UrlRedirectOnSuccess
HTTPTimeouttime.DurationNoTimeout for AuthKnight API calls. Defaults to 10 seconds

Step 3: Setup Routes

mux := http.NewServeMux()

// Auth routes (login, register, callback, etc.)
mux.HandleFunc("/auth/", authInstance.Router().ServeHTTP)

// Public home page with AuthKnight login link
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    loginURL := authInstance.LinkAuthKnightRedirect(r)
    fmt.Fprintf(w, "<h1>Home</h1><p><a href='%s'>Login with AuthKnight</a></p>", loginURL)
})

// Protected routes
mux.Handle("/dashboard", authInstance.WebAuthOrRedirectMiddleware(dashboardHandler))

http.ListenAndServe(":8084", mux)

URL Helpers

// Build the full AuthKnight login URL from the request (scheme + host)
loginURL := authInstance.LinkAuthKnightRedirect(r)

// Or build manually with custom back_url and next_url
loginURL := authInstance.LinkAuthKnightLogin(cancelURL, callbackURL)

// Get the callback path (relative to endpoint)
callbackPath := authInstance.LinkAuthKnightCallback()

back_url vs next_url

LinkAuthKnightRedirect(r) derives both automatically from the request:

Page Behavior in AuthKnight Mode

RouteBehavior
/auth/loginRedirects to AuthKnight's hosted login page
/auth/register (without verified_key)Redirects to AuthKnight's hosted login page
/auth/register?verified_key=xxxShows registration form with verified email as static text
/auth/password-restoreRedirects to AuthKnight login
/auth/password-resetRedirects to AuthKnight login
/auth/login-code-verifyRedirects to AuthKnight login
/auth/register-code-verifyRedirects to AuthKnight login
/auth/api/authknight/callbackInternal endpoint that AuthKnight redirects to after auth

Security: Verified Email Protection

The verified email on the registration page is displayed as static text (not an input field) to prevent browser extensions like LastPass from modifying it. The email used for registration is always retrieved from the secure temporary key store, not from the form submission.

How it works:

  1. AuthKnight verifies the email and sends it to your callback
  2. Your server stores the email in a temporary key (verified_key → email)
  3. User is redirected to /auth/register?verified_key=xxx
  4. The register page retrieves the email from the temporary key store (server-side)
  5. The email is displayed as static text with a hidden input for form submission
  6. The register API handler also retrieves the email from the temporary key store, not from the form

This means even if a browser extension or manual manipulation changes the form's email field, the actual email used for registration comes from the server-side temporary key store.

Endpoints

API Endpoint

MethodEndpointDescription
GET/auth/api/authknight/callbackAuthKnight redirect endpoint (receives once token)

Page Endpoints

MethodEndpointDescription
GET/auth/loginRedirects to AuthKnight login
GET/auth/registerRegistration page (redirects without verified_key)
GET/auth/logoutLogout page

Multi-Method Usage

AuthKnight can run alongside other login methods via NewMultiMethodAuth:

knight, _ := auth.NewAuthKnightAuth(cfgKnight)
a, _ := auth.NewOtpAuth(cfgOtp)

mma, _ := auth.NewMultiMethodAuth("/auth", []types.Method{
    auth.NewAuthKnightMethod(knight), // primary → /auth/login redirects out
    auth.NewOTPMethodWithPage(a, nil), // secondary → /auth/login-otp
})

When types.PostAuthHooks are configured, the callback runs through the shared post-auth pipeline (allowlist → find-or-create → session → cookie → redirect) instead of the legacy token-store path — new users are created automatically and land on the register-completion redirect.

Working Example

A complete working example is available in examples/authknight:

cd examples/authknight
go run main.go

Available at http://localhost:8084.