The Frontegg Go SDK lets you validate Frontegg JWTs, protect HTTP routes, check feature entitlements, and call the Frontegg API with idiomatic Go ergonomics.
Prerequisites
Prerequisites
Go ≥ 1.24 and a Frontegg workspace with a Client ID and API Key (Frontegg Portal → Settings → API Tokens).
go get github.com/frontegg/go-sdkInitialize the SDK once at application startup with your workspace credentials. The package-level Init sets up a default client used by the HTTP middleware.
Regions
Regions
The SDK defaults to Frontegg's EU region. If you're running in another region, set FRONTEGG_API_GATEWAY_URL to your region's URL instead of api.frontegg.com.
import "github.com/frontegg/go-sdk"
func main() {
frontegg.Init(frontegg.Credentials{
ClientID: "<YOUR_CLIENT_ID>",
APIKey: "<YOUR_API_KEY>",
})
// ...
}If you prefer an explicit, dependency-injected client instead of the package-level default, construct one and build sub-clients from it:
client := frontegg.New(frontegg.Credentials{
ClientID: "<YOUR_CLIENT_ID>",
APIKey: "<YOUR_API_KEY>",
})
identity := client.Identity()
entitlements := client.Entitlements()Environment variables
Environment variables
Credentials can also be provided through the FRONTEGG_CLIENT_ID and FRONTEGG_API_KEY environment variables. Values passed in code take precedence.
Use the WithAuthentication middleware to protect your routes. It reads the token from the Authorization: Bearer <token> header or the x-api-key header, validates it, optionally enforces roles and permissions, and stores the decoded user on the request context.
import (
"net/http"
"github.com/frontegg/go-sdk"
"github.com/frontegg/go-sdk/middleware"
)
func main() {
frontegg.Init(frontegg.Credentials{
ClientID: "<YOUR_CLIENT_ID>",
APIKey: "<YOUR_API_KEY>",
})
// This route is accessible only to authenticated users with the "admin" role.
protected := frontegg.WithAuthentication(middleware.Options{
Roles: []string{"admin"},
Permissions: []string{"fe.secure.read"},
})
mux := http.NewServeMux()
mux.Handle("/protected", protected(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, _ := middleware.UserFromContext(r.Context())
// user.ID(), user.Email, user.TenantID, user.Roles, user.Permissions
_, _ = w.Write([]byte("hello " + user.Email))
})))
_ = http.ListenAndServe(":8080", mux)
}Behavior
- No credentials →
401 Unauthenticated. - Invalid token, or missing required role/permission → the error's status code (
401for authentication failures,403for authorization failures). - On success, the decoded entity is available via
middleware.UserFromContext(ctx).
Roles and Permissions are satisfied when the token contains at least one of the listed values.
The middleware works with the standard library and any router built on net/http (chi, gorilla, http.ServeMux, and others).
If you don't want to use the package-level default, pass an identity client directly:
guard := middleware.WithAuthentication(client.Identity(), middleware.Options{
Roles: []string{"admin"},
})When you use M2M authentication, access tokens are cached automatically. The in-memory cache is the default. To share a cache across multiple instances, use the Redis backend (only applications that import it pull in the go-redis dependency):
import (
"github.com/redis/go-redis/v9"
"github.com/frontegg/go-sdk/cache/redisstore"
)
rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
store := redisstore.New[MyType](rdb) // implements cache.Cache[MyType]Frontegg provides various clients for seamless integration with the Frontegg APIs. The entitlements client can be used to determine whether an authorized user or tenant is entitled to a particular feature or permission. This client is discussed in detail in Entitlements quickstart.
Frontegg's Managed Audit Logs feature allows collecting custom audit logs that are specific for your application and displaying these in Frontegg's self-service component.
import "github.com/frontegg/go-sdk/audits"
ac := client.Audits()
if err := ac.Init(ctx, "<CLIENT_ID>", "<AUDITS_KEY>"); err != nil {
log.Fatal(err)
}_ = ac.SendAudit(ctx, audits.SendAuditParams{
TenantID: "my-tenant-id",
Severity: audits.SeverityMedium,
Fields: map[string]any{
"user": "info@frontegg.com",
"resource": "Portal",
"action": "Login",
"ip": "1.2.3.4",
},
})page, err := ac.GetAudits(ctx, audits.GetAuditsParams{
TenantID: "my-tenant-id",
Filter: "any-text-filter",
SortBy: "createdAt",
SortDirection: "desc",
Offset: 0,
Count: 50,
})
// page.Data, page.TotalThe audits client also exposes GetAuditsStats, GetAuditsMetadata, and SetAuditsMetadata.
import "github.com/frontegg/go-sdk/events"
ev := client.Events(auth) // auth: an initialized authenticator
eventID, err := ev.Send(ctx, "my-tenant-id", events.EventTrigger{
EventKey: "user.invited",
Data: events.EventProperties{
Title: "You're invited",
Description: "Join the Acme team",
},
})
status, err := ev.GetStatus(ctx, eventID)Frontegg provides a comprehensive REST API for your application. In order to use the API from your backend it is required to initialize the client and the authenticator which maintains the backend to backend session.
import "github.com/frontegg/go-sdk/httpclient"
auth := client.NewAuthenticator()
if err := auth.Init(ctx, "<YOUR_CLIENT_ID>", "<YOUR_API_KEY>"); err != nil {
log.Fatal(err)
}
api := client.HTTPClient(auth, httpclient.WithBaseURL("https://api.frontegg.com"))
resp, err := api.Post(ctx, "identity/resources/auth/v1/user",
map[string]string{
"email": "johndoe@acme.com",
"password": "my-super-duper-password",
},
// Optional per-request headers. "frontegg-vendor-host" rewrites the host.
map[string]string{"frontegg-vendor-host": "acme.frontegg"},
)
if err != nil {
log.Fatal(err)
}
var user map[string]any
_ = resp.JSON(&user)Get, Post, Put, Patch, and Delete are available; each returns a *Response with StatusCode, Header, and a JSON(&v) helper.
The hosted-login flow uses PKCE, as required by OAuth 2.1. RequestAuthorize returns a code_verifier that you must persist (for example, in the user's session keyed by state) and pass back to CodeExchange.
import "github.com/frontegg/go-sdk/hostedlogin"
hl := client.HostedLogin("https://your-app.com/oauth/callback")
// 1. Build the authorization URL and keep the verifier.
authReq, err := hl.RequestAuthorize(ctx, "csrf-state-token")
// Redirect the user to authReq.URL.
// Store authReq.CodeVerifier, keyed by authReq.State.
// 2. On the callback (with ?code= and ?state= from the query string):
res, err := hl.CodeExchange(ctx, code, state, savedCodeVerifier)
// res.User → the decoded user profile
// res.AccessToken → access token
// res.RefreshToken → refresh tokenRedirect URI
Redirect URI
The redirect_uri you pass to HostedLogin must exactly match an allowed callback configured on your Frontegg application.
Frontegg provides an identity client that can be utilized for validating Frontegg JWTs outside the middleware flow.
import "github.com/frontegg/go-sdk/identity"
ident := client.Identity()
user, err := ident.ValidateToken(ctx, bearerToken, &identity.ValidateTokenOptions{
Roles: []string{"admin"},
Permissions: []string{"fe.secure.read"},
WithRolesAndPermissions: true, // hydrate roles & permissions on the entity
}, identity.JWTHeader)
if err != nil {
// handle authentication / authorization failure
}ValidateToken takes the header type as its last argument:
identity.JWTHeader— a Frontegg JWT (from theAuthorizationheader).identity.AccessTokenHeader— an API key (from thex-api-keyheader).
Permissions in JWTs
Permissions in JWTs
Some JWTs do not include permissions in their payload. Permissions are required for entitlement checks, so set WithRolesAndPermissions: true when you need them.
Verify that a user has completed step-up (MFA) authentication, optionally within a maximum session age:
// Require step-up.
user, err := ident.ValidateToken(ctx, token, &identity.ValidateTokenOptions{
StepUp: &identity.StepUpOptions{},
}, identity.JWTHeader)
// Require step-up performed within the last hour.
user, err = ident.ValidateToken(ctx, token, &identity.ValidateTokenOptions{
StepUp: &identity.StepUpOptions{MaxAge: 3600},
}, identity.JWTHeader)Service URLs default to the public Frontegg gateway and can be overridden via environment variables — useful for EU/regional or self-hosted deployments.
| Variable | Default | Description |
|---|---|---|
FRONTEGG_CLIENT_ID | — | Vendor client ID (used when not passed in code) |
FRONTEGG_API_KEY | — | Vendor API key (used when not passed in code) |
FRONTEGG_API_GATEWAY_URL | https://api.frontegg.com | Base URL for all services |
FRONTEGG_AUTHENTICATOR_NUMBER_OF_TRIES | 3 | Number of authentication retry attempts |
FRONTEGG_IDENTITY_SERVICE_URL | <base>/identity | Identity service URL |
FRONTEGG_ENTITLEMENTS_SERVICE_URL | <base>/entitlements | Entitlements service URL |
FRONTEGG_AUDITS_SERVICE_URL | <base>/audits/ | Audits service URL |
FRONTEGG_EVENT_SERVICE_URL | <base>/event | Events service URL |
FRONTEGG_OAUTH_SERVICE_URL | <base>/oauth | OAuth service URL |
FRONTEGG_VENDORS_SERVICE_URL | <base>/vendors | Vendors service URL |
FRONTEGG_METADATA_SERVICE_URL | <base>/metadata/ | Metadata service URL |
Errors are returned as values and are inspectable with errors.Is and errors.As. Identity errors also carry an HTTP status code.
import "errors"
_, err := ident.ValidateToken(ctx, token, opts, identity.JWTHeader)
switch {
case errors.Is(err, identity.ErrInsufficientRole): // 403
case errors.Is(err, identity.ErrInsufficientPermission): // 403
case errors.Is(err, identity.ErrFailedToAuthenticate): // 401
}
// Inspect the status code directly:
var sce *identity.StatusCodeError
if errors.As(err, &sce) {
http.Error(w, sce.Message, sce.StatusCode)
}- Context: every method that performs I/O takes a
context.Contextas its first argument. - Concurrency: all clients are safe for concurrent use.
- Errors: returned as values; no panics for expected failures.
- Token refresh: the authenticator refreshes the vendor token automatically before it expires.
- Go Entitlements — feature and permission checks
- Source & issues — Go SDK on GitHub