AnnouncementsIntroducing Arkoren

    Menu

    API Tokens

    Accept bearer credentials and manage application-issued API keys with hashed storage and revocation.

    What's on this page
    1. Connect a bearer verifier and reject presented invalid credentials without falling back.
    2. Issue random API keys, store their hashes, and verify current key and account state.
    3. Plan key revocation and understand how offline JWT verification changes that boundary.
    On this page

    A program authenticates by sending a secret token with each request, rather than signing in through a browser form. auth.Bearer extracts that token and delegates verification to your application. It does not issue tokens, maintain a token store, or implement a JWT verification policy.

    If you already have a verifier, connect it directly. If your service issues its own credentials, a random API key backed by a stored hash gives you a per-key revocation point. For identity-provider JWTs, use OIDC Tokens instead of building another verifier.

    Accept Bearer Credentials

    Serve credential-bearing requests over HTTPS. Send the token in the header, never in a URL, and keep it out of logs:

    GET /api/invoices HTTP/1.1
    Authorization: Bearer ak_example_token

    The value above illustrates the header, not a usable key. With your existing router group api and verifyKey func(context.Context, string) (User, error), import auth from arkoren.dev/core/http/auth and register:

    api.Use(auth.Middleware(auth.Bearer(verifyKey)))
    api.Use(auth.Require[User]())

    auth.Middleware alone allows missing credentials; Require closes that path for private routes. Your verifier must return the same exact User type used by handlers and policies. A zero-valued user returned without an error still counts as authenticated, so reject unknown or disabled accounts explicitly.

    The bearer authenticator accepts a single Authorization header with a case-insensitive Bearer scheme and a nonempty token containing no spaces. No header returns auth.ErrNoCredentials. A malformed header is rejected before verification; a verifier error normally produces 401 with WWW-Authenticate: Bearer error="invalid_token".

    Never return auth.ErrNoCredentials for a presented invalid, expired, or revoked token. That error permits fallback or anonymous handling. Return a different error, such as auth.ErrUnauthenticated.

    HTTP responses do not expose verifier error messages, but retain the error as a cause. Database failures are also rendered as invalid-token responses through this adapter; inspect controlled diagnostics to distinguish them from bad keys. Do not put raw tokens or sensitive account details in those causes.

    Issue Keys

    An API key is a bearer secret: anyone holding it can use its authority. Issue it only after authenticating the owner and authorizing key creation. The owner ID must come from that trusted identity, not from a submitted request field. The application types below separate user input from the stored record:

    type User struct {
    	ID int
    }
    
    type IssueKeyInput struct {
    	Name string `json:"name"`
    }
    
    type APIKey struct {
    	UserID  int
    	Hash    []byte
    	Name    string
    	Revoked bool
    }

    With validated in IssueKeyInput, authenticated owner User, an operation context ctx, and your application repository keys, generate a random key. Import standard-library crypto/rand and crypto/sha256:

    token := "ak_" + rand.Text()
    digest := sha256.Sum256([]byte(token))
    err := keys.Save(ctx, APIKey{
    	UserID: owner.ID,
    	Hash:   digest[:],
    	Name:   in.Name,
    })

    rand.Text provides at least 128 bits of randomness; the prefix only identifies your key format. Store the hash, not the original token. Return the token once only after Save succeeds, using an authenticated response that is not cached or logged. It cannot be recovered from the stored record; lost keys need replacement.

    A fast SHA-256 hash is suitable for a high-entropy random secret. It is not appropriate for human-chosen passwords, which need password hashing. Hashed storage reduces exposure if the key table leaks; it does not protect keys already stolen from clients or exposed during issuance.

    Verify Stored Keys

    Keep a unique lookup index on the stored hash. Your keys.ByHash application method returns an APIKey or an error; absence must not be reported as auth.ErrNoCredentials. Hash the entire presented token before looking it up:

    verifyKey := func(ctx context.Context, token string) (User, error) {
    	digest := sha256.Sum256([]byte(token))
    	stored, err := keys.ByHash(ctx, digest[:])
    	if err != nil {
    		return User{}, err
    	}
    	if stored.Revoked {
    		return User{}, auth.ErrUnauthenticated
    	}
    
    	return loadActiveUser(ctx, stored.UserID)
    }

    loadActiveUser is your func(context.Context, int) (User, error) account lookup. It returns an error for missing or disabled accounts, never a zero user with nil error or ErrNoCredentials. If keys have expiry or granted permissions, check expiry here and carry the permitted authority in a request-local identity; authorization still decides which actions and resources it allows.

    auth.Bearer checks the header shape, not your key format. Very short tokens can reach the verifier. If your format extracts a public lookup identifier or prefix, validate lengths before slicing. When comparing raw secrets rather than looking up a full digest, use crypto/subtle.ConstantTimeCompare, not ordinary equality.

    Revoke Keys

    Delete a key record or mark it revoked, and ensure every verification checks that state. The next uncached lookup rejects the key. If you cache verification results, invalidate those entries too; otherwise cache lifetime delays revocation. Already authenticated requests are not canceled by changing the record.

    Let users name keys and revoke individual credentials without changing every client. For replacement, issue a new key, move the client to it, then revoke the old one. Scope key-management operations to the authenticated owner and apply authorization separately; possession of an arbitrary key does not automatically grant permission to manage other credentials.

    Choose Token Semantics

    A signed JWT can carry identity claims without a per-request database lookup. That saves shared-state access but changes the revocation boundary: a purely offline verifier cannot observe an account deletion, permission change, or key revocation until its verification policy makes the token unacceptable, commonly at expiry. Short lifetimes reduce this window; a server-side denylist or account lookup adds state back to verification.

    If you choose JWTs, use a maintained verifier with an explicit trusted issuer, intended audience, allowed algorithms and keys, expiry policy, and provider-specific token-purpose checks. Decoding claims or checking a signature alone is not authentication. Arkoren’s core bearer adapter does not implement these checks or provide a token endpoint, refresh flow, or authorization server.

    Separate Browser Routes

    Prefer bearer-only API groups when scripts should not inherit browser session authority. Such routes do not need CSRF protection for bearer authentication because the credential is explicitly supplied, not automatically sent as a cookie. Do not apply that exception to routes that also accept sessions.

    If the same routes deliberately accept both credentials, configure the session and CSRF middleware and choose authenticator order. Both authenticators must return the same identity type; the first successful one wins even if the request also carries another credential. For MCP, reuse the verifier and follow endpoint security for protocol and tool-access rules.

    For all functions and options, see the auth API reference.

    Open a bulk documentation archive?

    This file contains every guide in its scope, not API declarations. Loading it all into an agent can exhaust its context window and increase processing time and cost. For coding tasks, start with the index and read only the relevant pages.

    Use the full archive for offline indexing, bulk processing, or deliberate large-context use.

    Browse the index instead
    Open archive