Skip to main content

Go Quickstart

Wire LumoAuth into a Go web app using the official lumo-auth-go SDK. The WebAuth.Router() mounts /login, /callback, /logout; WebAuth.Middleware populates the request context with the signed-in user; lumoauth.UserFromContext(ctx) reads it back out.

Prerequisites​

  • Go 1.22+
  • An OAuth client registered with redirect URI http://localhost:3000/auth/callback.

Register the client in the portal under Applications → New application: enter an Application name, choose Web application, enter the Redirect URI, and click Create application. The client secret is shown once, on the page you land on.

The Create application form with Web application selected and a redirect URI entered

The Quickstarts page (under Applications & developer tools) has a Go tab that shows the .env values for your organization and client pre-filled, ready to copy.

The Quickstarts page with the Go tab selected, showing pre-filled environment variables

The tab's first step lists only OAuth clients of the kind these snippets need; if none exists, Create one for this framework opens the create form with the right type preselected and brings you back. Its last step, Check it worked, shows the first sign-in that completes through the selected client.

Or scaffold with the CLI:

lumo login --org your-org-slug
lumo init --framework go --dir ./my-app
cd ./my-app && go mod tidy && go run .

Manual install​

go get github.com/lumoauth/lumo-auth-go \
github.com/go-chi/chi/v5 \
github.com/joho/godotenv

.env:

LUMO_BASE_URL=https://app.lumoauth.dev
LUMO_ORG_ID=your-org-slug
LUMO_CLIENT_ID=your-oauth-client-id
LUMO_CLIENT_SECRET=your-oauth-client-secret
SESSION_SECRET=change-me-to-a-random-32-byte-string
PORT=3000

main.go:

package main

import (
"fmt"
"log"
"net/http"
"os"

"github.com/go-chi/chi/v5"
"github.com/joho/godotenv"
lumoauth "github.com/lumoauth/lumo-auth-go"
)

func main() {
_ = godotenv.Load()

auth, err := lumoauth.NewWebAuth(lumoauth.WebConfig{
BaseURL: os.Getenv("LUMO_BASE_URL"),
Organization: os.Getenv("LUMO_ORG_ID"),
ClientID: os.Getenv("LUMO_CLIENT_ID"),
ClientSecret: os.Getenv("LUMO_CLIENT_SECRET"),
SessionSecret: os.Getenv("SESSION_SECRET"),
CallbackPath: "/auth/callback",
PostLoginRedirect: "/",

// Session cookie defaults to Secure: true, so browsers won't send
// it over plain HTTP. Opt out for localhost dev; remove this line
// (or use lumoauth.Bool(true)) in production over HTTPS.
CookieSecure: lumoauth.Bool(false),
})
if err != nil {
log.Fatal(err)
}

r := chi.NewRouter()
r.Mount("/auth", auth.Router())

// Middleware reads the signed-in user from the session into the
// request context. UserFromContext returns nil for anonymous requests.
r.Group(func(r chi.Router) {
r.Use(auth.Middleware)

r.Get("/", func(w http.ResponseWriter, req *http.Request) {
user := lumoauth.UserFromContext(req.Context())
if user == nil {
http.Redirect(w, req, "/auth/login", http.StatusFound)
return
}
fmt.Fprintf(w, "<h1>Hi %s</h1><a href=\"/auth/logout\">Sign out</a>", user.Email)
})

// Gate with RequireAuth — 401 if no user.
r.With(auth.RequireAuth).Get("/api/me", func(w http.ResponseWriter, req *http.Request) {
user := lumoauth.UserFromContext(req.Context())
fmt.Fprintf(w, "%s\n", user.Email)
})
})

port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
log.Printf("listening on http://localhost:%s", port)
log.Fatal(http.ListenAndServe(":"+port, r))
}

Run with go run ., visit http://localhost:3000 — you'll be redirected to LumoAuth, sign in, and bounce back with the user in context.

net/http (no chi)​

The router and middleware are standard http.Handler types — the chi example above is for ergonomic grouping, but plain net/http works just as well:

mux := http.NewServeMux()
// Strip /auth prefix and delegate to auth.Router() on each subpath.
mux.Handle("/auth/", http.StripPrefix("/auth", auth.Router()))
mux.Handle("/", auth.Middleware(http.HandlerFunc(homeHandler)))
mux.Handle("/api/me", auth.Middleware(auth.RequireAuth(http.HandlerFunc(meHandler))))
http.ListenAndServe(":3000", mux)

Behind a reverse proxy​

The middleware honours X-Forwarded-Proto and X-Forwarded-Host when constructing the absolute callback URL, so you can run the Go process on plain HTTP behind a TLS-terminating proxy and the redirect URI sent to LumoAuth still resolves to your public https://... URL.

Production checklist​

  • SessionSecret is ≥32 bytes of cryptographic randomness.
  • The session cookie's Secure flag is on. The SDK defaults CookieSecure to true; set CookieSecure: lumoauth.Bool(false) only for plain-http local dev, and leave it unset (or true) in production.
  • Rotate SessionSecret periodically — gorilla/sessions accepts multiple keys for graceful rotation.
  • OAuth client's redirect URI exactly matches the production URL.
  • If running behind a proxy, configure it to set X-Forwarded-Proto: https.

Reference​