Skip to content
Last updated

Protect your backend APIs with Go SDK

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

Go ≥ 1.24 and a Frontegg workspace with a Client ID and API Key (Frontegg Portal → Settings → API Tokens).

Install the SDK

go get github.com/frontegg/go-sdk

Initialize

Initialize 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

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

Credentials can also be provided through the FRONTEGG_CLIENT_ID and FRONTEGG_API_KEY environment variables. Values passed in code take precedence.

Protect backend routes

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 (401 for authentication failures, 403 for 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).

Using an explicit client

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"},
})

Use access tokens

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 clients

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.

Create a new audits client

import "github.com/frontegg/go-sdk/audits"

ac := client.Audits()
if err := ac.Init(ctx, "<CLIENT_ID>", "<AUDITS_KEY>"); err != nil {
	log.Fatal(err)
}

Sending audits

_ = 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",
	},
})

Fetching audits

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.Total

The audits client also exposes GetAuditsStats, GetAuditsMetadata, and SetAuditsMetadata.

Events

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)

Working with the REST API

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.

Hosted login (OAuth 2.1 + PKCE)

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 token

Redirect URI

The redirect_uri you pass to HostedLogin must exactly match an allowed callback configured on your Frontegg application.

Validating JWT

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 the Authorization header).
  • identity.AccessTokenHeader — an API key (from the x-api-key header).

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.

Step-up authentication

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)

Configuration

Service URLs default to the public Frontegg gateway and can be overridden via environment variables — useful for EU/regional or self-hosted deployments.

VariableDefaultDescription
FRONTEGG_CLIENT_IDVendor client ID (used when not passed in code)
FRONTEGG_API_KEYVendor API key (used when not passed in code)
FRONTEGG_API_GATEWAY_URLhttps://api.frontegg.comBase URL for all services
FRONTEGG_AUTHENTICATOR_NUMBER_OF_TRIES3Number of authentication retry attempts
FRONTEGG_IDENTITY_SERVICE_URL<base>/identityIdentity service URL
FRONTEGG_ENTITLEMENTS_SERVICE_URL<base>/entitlementsEntitlements service URL
FRONTEGG_AUDITS_SERVICE_URL<base>/audits/Audits service URL
FRONTEGG_EVENT_SERVICE_URL<base>/eventEvents service URL
FRONTEGG_OAUTH_SERVICE_URL<base>/oauthOAuth service URL
FRONTEGG_VENDORS_SERVICE_URL<base>/vendorsVendors service URL
FRONTEGG_METADATA_SERVICE_URL<base>/metadata/Metadata service URL

Error handling

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)
}

Conventions

  • Context: every method that performs I/O takes a context.Context as 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.