Authentication establishes who sent a request. Arkoren resolves a session or bearer credential into a Go value that your application owns; it supplies no user table, registration flow, or login screen. Authorization then decides what that identity may do.
Use Session Login for passwords and browser sessions, API Tokens for application-issued keys, OAuth Login for external browser sign-in, or OIDC Tokens for provider-issued JWTs. All four use the same request identity model.
Focused snippets keep error results visible but omit routine propagation. Checks remain where failure changes authentication or access. Handle all errors in your application.
Define Identity
Choose one identity type for handlers and policies. An invoice application might use this local account value:
type User struct {
ID int
Name string
Disabled bool
}
No field or interface is required by Arkoren. Your verifier or loader must
reject unknown and disabled accounts before returning success. Returning
User{}, nil still authenticates a request; auth.Require checks presence,
not whether fields are nonzero. The same applies to a typed nil pointer returned
without an error.
Identity lookup uses the exact Go type. User, *User, and a separately defined
type with identical fields are different identities; Arkoren does not convert
between them.
Resolve Credentials
An authenticator is a func(*http.Request) (User, error) that verifies
credentials and returns your identity. Import auth from
arkoren.dev/core/http/auth. Its result controls whether another authenticator
can try:
| Result | Middleware behavior |
|---|---|
| User and nil error | Store that user and stop trying authenticators. |
auth.ErrNoCredentials, including wrapped errors |
Try the next authenticator. |
| Any other error | Stop the request without calling its handler. |
Return ErrNoCredentials only when the request has no credentials that this
authenticator handles. A presented invalid bearer token must return a different
error. Do not trust a client-supplied user ID or header as proof of identity.
For routes accepting both browser sessions and API keys, use your existing
loadUser (func(context.Context, int) (User, error)) and verifyToken
(func(context.Context, string) (User, error)):
identity := auth.NewSessionIdentity(loadUser)
r.Use(auth.Middleware(
identity.Authenticator(),
auth.Bearer(verifyToken),
))
Here r is your existing router. This browser group already needs
session and CSRF middleware before authentication.
The first success wins: a valid session in this order prevents inspection of a
simultaneously supplied bearer header. An error stops the chain; it does not
trigger fallback. Prefer separate browser and API groups when they need different
credential policies.
Choose Route Access
Authentication middleware permits anonymous requests when every authenticator
returns ErrNoCredentials. This lets public pages optionally personalize their
response. To require a user for private routes, add auth.Require after the
authentication middleware:
private := r.With(auth.Require[User]())
private.Get("/invoices", listInvoices)
listInvoices is your existing handler. An anonymous request receives a 401
Problem Details response with WWW-Authenticate: Bearer; an authenticated
request reaches the handler. This proves identity presence, not permission to
read every invoice. Apply your authorization rules
separately.
For browser pages, a 401 does not display a login form. If you want a login
redirect, write middleware that checks auth.FromContext[User] and redirects to
a fixed local login path. Validate any return target rather than redirecting to
an arbitrary request parameter. See middleware scope
for applying different behavior to different route groups.
Read Identity
Handlers retrieve the exact type stored by authentication. With request and
response from their arkoren.dev/core/http/ packages and standard-library
net/http, read the local user before returning a profile:
user, err := request.User[User](r)
if err != nil {
return err
}
return response.JSON(w, http.StatusOK, user)
w and r are the handler’s response writer and request. Missing identities
return auth.ErrUnauthenticated, including when a different identity type is
present. In context-only code, auth.FromContext[User](ctx) returns the value
and a presence boolean instead.
Tests can use auth.NewContext(ctx, User{ID: 42}) without performing a login.
That helper trusts the supplied value; keep it in trusted code, not in a path
that copies unverified request input. Auth Tests
also covers testing the actual authenticators.
Handle Rejections
To change the anonymous response, configure auth.Builder.ErrorResponse in
auth.Require[User](...). It replaces the whole problem and its headers, so
include the appropriate authentication challenge if you keep status 401.
A zero response restores the default. This setting does not change the error
returned by request.User or failures returned by an authenticator.
If you need anonymous-rejection events, supply your existing event bus:
private := r.With(auth.Require[User](func(required *auth.Builder) {
required.Events(app.Events())
}))
Every rejection publishes http.auth.rejected, even with a custom status.
Its payload contains Method, not a request path, identity, query, or cause.
HTTP methods and event context values can still be untrusted or sensitive;
normal Problem Details rendering can include the request path as instance.
Keep listeners cheap and apply rate limiting
before the required-user middleware when needed. No event bus means no event.
For all functions and options, see the
auth API reference.