OAuth2 Server (Passport)
Passport turns your Nimbus app into a full OAuth2 authorization server, so third-party (and first-party) applications can obtain tokens to call your API on a user's behalf. It supports the authorization_code grant (with PKCE), client_credentials, and refresh_token, plus token introspection (RFC 7662) and revocation (RFC 7009). Tokens are opaque and stored hashed, so they are fully revocable.
Installation
import "github.com/CodeSyncr/nimbus/plugins/passport"
app.Use(passport.NewPlugin(db, passport.Config{}))
The plugin migrates four tables (oauth_clients, oauth_auth_codes, oauth_access_tokens, oauth_refresh_tokens), mounts the OAuth endpoints under /oauth, and binds the server as passport in the container. Defaults: access token 1h, refresh token 30 days, auth code 10m, PKCE required for public clients.
Endpoints
| Method & Path | Purpose |
|---|---|
| GET /oauth/authorize | Consent screen (requires a logged-in user) |
| POST /oauth/authorize | Approve/deny → redirect back with ?code= |
| POST /oauth/token | Exchange a grant for tokens |
| POST /oauth/introspect | Token introspection (client-authenticated) |
| POST /oauth/revoke | Revoke an access token |
Registering clients
Resolve the server and create a client. Confidential clients get a secret (shown once); public clients (SPA/mobile/CLI) get no secret and must use PKCE.
srv := app.Container.MustMake("passport").(*passport.Server)
res, _ := srv.CreateClient(ctx, "Acme Integration", ownerUserID,
[]string{"https://acme.test/callback"}, // redirect allowlist
[]string{passport.GrantAuthorizationCode, passport.GrantRefreshToken},
[]string{"read:profile", "write:posts"}, // allowed scopes
true) // confidential
// res.Client.ClientID and res.PlainSecret — store the secret now; it is hashed at rest.
The authorization code flow (with PKCE)
- Client redirects the user to
/oauth/authorize?response_type=code&client_id=…&redirect_uri=…&scope=read:profile&state=…&code_challenge=…&code_challenge_method=S256. - The user logs in (if needed) and approves the consent screen.
- Passport redirects back to the client with
?code=…&state=…. - The client POSTs to
/oauth/tokenwithgrant_type=authorization_code, thecode,redirect_uri, andcode_verifierand receives an access + refresh token.
Client credentials may be sent via HTTP Basic auth or in the form body. Auth codes are single-use and short-lived; refresh tokens rotate on use (the old pair is revoked).
Protecting your API (resource server)
srv := app.Container.MustMake("passport").(*passport.Server)
api := app.Router.Group("/api", passport.RequireAccessToken(srv))
api.Use(passport.RequireScope("read:profile"))
api.Get("/me", func(c *http.Context) error {
at := passport.AccessTokenFrom(c.Ctx()) // token record: UserID, Scopes…
return c.JSON(200, map[string]string{"user": at.UserID})
})
RequireAccessToken validates the Bearer token (401 + WWW-Authenticate on failure); RequireScope enforces a granted scope (403 otherwise). The wildcard scope * grants all.