Skip to content
  • SDKs and APIs
  • Back end SDKs

Go SDK

The Kinde Go SDK allows developers to connect their Go applications to Kinde. It provides OAuth 2.0 flow implementations, JWT parsing and validation, helpers for working with permissions, roles, feature flags and entitlements, and a client for the Kinde Management API.

You can also view the Go SDK source on GitHub.

  • A Kinde account with Admin or Engineer permissions (Sign up for free)
  • Golang version 1.24 or higher
  1. Add the SDK to your project using the following terminal command:

    Terminal window
    go get github.com/kinde-oss/kinde-go
  2. Then import the packages you need:

    import (
    "github.com/kinde-oss/kinde-go/oauth2/authorization_code" // browser / device auth flows
    "github.com/kinde-oss/kinde-go/oauth2/client_credentials" // machine-to-machine flow
    "github.com/kinde-oss/kinde-go/jwt" // JWT parsing and validation
    "github.com/kinde-oss/kinde-go/kinde" // Management API client
    )

Supported OAuth 2.0 flows

Link to this section

The SDK implements three OAuth 2.0 flows. Choose the package that matches your application type:

FlowUse case
Authorization CodeServer-rendered web apps with user interaction.
Device AuthorizationCLIs, TVs, and other devices with limited input.
Client CredentialsMachine-to-machine and Management API access (no user).
  • CLI example - device authorization flow with secure keychain token storage.
  • Gin chat example - authorization code flow integrated with a Gin web application.

Authorization code flow (backend web app)

Link to this section

1. Create a Kinde Go backend app

Link to this section
  1. Go to your Kinde dashboard, select Add new application
  2. Set a Name and select the application type as Back-end web > Go
  3. Select Save
  4. Go to Details page and copy the Domain (or custom domain), Client ID and Client secret values.
  5. Add callback URLs, for example:
    • Allowed callback URLs (also known as redirect URIs) — http://localhost:8080/callback
    • Allowed logout redirect URLs — http://localhost:8080
    For production, add the same paths using your real domain.
  6. Select Save.
  7. Go to Authentication page and select the authentication methods you want your users to sign in or sign up with (e.g., Google, Facebook, etc.).
  8. Select Save.

2. Import packages

Link to this section
  1. Import the packages for the authorization code flow:

    import (
    "github.com/kinde-oss/kinde-go/oauth2/authorization_code"
    "github.com/kinde-oss/kinde-go/jwt"
    )

3. Add environment variables

Link to this section
  1. Add your Kinde credentials to a .env file (or your environment config):

    KINDE_DOMAIN=https://your-tenant.kinde.com
    KINDE_CLIENT_ID=your_client_id_here
    KINDE_CLIENT_SECRET=your_client_secret_here
    KINDE_REDIRECT_URI=http://localhost:8080/callback
  2. Load them at startup with a package like godotenv, or read them directly with os.Getenv. Replace the placeholder values with the credentials you copied from the Details page in step 1.

4. Create the authorization code flow

Link to this section

The flow requires a WithSessionHooks(...) implementation to store and retrieve tokens between requests. You need to satisfy the ISessionHooks interface, which has these eight methods:

GetRawToken() (*oauth2.Token, error)
SetRawToken(token *oauth2.Token) error
GetState() (string, error)
SetState(state string) error
GetCodeVerifier() (string, error)
SetCodeVerifier(codeVerifier string) error
GetPostAuthRedirect() (string, error)
SetPostAuthRedirect(redirect string) error

For a reference implementation using Gin sessions, see the SessionStorage example in the SDK repo. For production web apps, back your implementation with cookies, a database, or Redis — see Session management for guidance. Once you have a session hooks implementation do the following:

  1. Initialize the authorization code flow:

    kindeAuthFlow, err := authorization_code.NewAuthorizationCodeFlow(
    os.Getenv("KINDE_DOMAIN"),
    os.Getenv("KINDE_CLIENT_ID"),
    os.Getenv("KINDE_CLIENT_SECRET"),
    os.Getenv("KINDE_REDIRECT_URI"),
    authorization_code.WithSessionHooks(sessionHooks), // your ISessionHooks implementation
    authorization_code.WithOffline(), // adds offline scope and manages refresh tokens
    authorization_code.WithAudience("<your API audience>"),
    authorization_code.WithTokenValidation(
    true, // validate signature via JWKS
    jwt.WillValidateAlgorithm(), // validate alg is RS256
    jwt.WillValidateAudience("<your API audience>"),
    ),
    )
  2. Register a /login route that redirects the user to Kinde’s hosted login page:

    http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
    http.Redirect(w, r, kindeAuthFlow.GetAuthURL(), http.StatusFound)
    })

    GetAuthURL() generates the full authorization URL including a CSRF-safe state parameter.

5. Create the callback route

Link to this section
  1. Register a /callback route that handles the OAuth 2.0 callback:

    http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
    kindeAuthFlow.AuthorizationCodeReceivedHandler(w, r)
    })

    Or call ExchangeCode yourself:

    err := kindeAuthFlow.ExchangeCode(ctx, code, receivedState)

6. Display logged-in user information

Link to this section

After the user has authenticated, parse the stored token through your session hooks to access profile fields:

rawToken, err := sessionHooks.GetRawToken()
if err != nil {
http.Error(w, "not logged in", http.StatusUnauthorized)
return
}
token, err := jwt.ParseFromSessionStorage(
rawToken,
jwt.WillValidateWithJWKSUrl(os.Getenv("KINDE_DOMAIN")+"/.well-known/jwks.json"),
)
if err != nil || !token.IsValid() {
http.Error(w, "invalid token", http.StatusUnauthorized)
return
}
profile := token.GetUserProfile()
// Or access individual claims directly
claims := token.GetClaims()
email := claims["email"]
givenName := claims["given_name"]
lastName := claims["family_name"]
fullName := claims["name"]
picture := claims["picture"]
userID := token.GetSubject()

GetUserProfile() pulls from the ID token, so given_name, family_name, email, and picture are available. To guarantee the freshest data from Kinde, call the userinfo endpoint instead using the HTTP client:

client, err := kindeAuthFlow.GetClient(r.Context())
if err != nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
resp, err := client.Get(os.Getenv("KINDE_DOMAIN") + "/oauth2/v2/user_profile")
if err != nil {
http.Error(w, "request failed", http.StatusInternalServerError)
return
}
defer resp.Body.Close()

7. Protect a route

Link to this section
  1. Create a new middleware function that uses IsAuthenticated to guard any route. If the user has no valid session, redirect them to login:

    http.HandleFunc("/dashboard", func(w http.ResponseWriter, r *http.Request) {
    ok, err := kindeAuthFlow.IsAuthenticated(r.Context())
    if err != nil || !ok {
    http.Redirect(w, r, kindeAuthFlow.GetAuthURL(), http.StatusFound)
    return
    }
    // user is authenticated — serve the page
    w.Write([]byte("Welcome to your dashboard"))
    })
  2. To reuse this across multiple routes, wrap it in middleware:

    func requireAuth(flow authorization_code.IAuthorizationCodeFlow, next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
    ok, err := flow.IsAuthenticated(r.Context())
    if err != nil || !ok {
    http.Redirect(w, r, flow.GetAuthURL(), http.StatusFound)
    return
    }
    next(w, r)
    }
    }
    // Usage
    http.HandleFunc("/dashboard", requireAuth(kindeAuthFlow, dashboardHandler))
    http.HandleFunc("/settings", requireAuth(kindeAuthFlow, settingsHandler))
  1. Register a /logout route:

    http.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
    if err := kindeAuthFlow.Logout(); err != nil {
    http.Error(w, "logout failed", http.StatusInternalServerError)
    return
    }
    http.Redirect(w, r, os.Getenv("KINDE_LOGOUT_REDIRECT_URI"), http.StatusFound)
    })
    • Logout() clears the locally stored tokens.
    • The redirect to KINDE_LOGOUT_REDIRECT_URI completes sign-out on Kinde’s side.
  2. Add this variable to your .env:

    Terminal window
    KINDE_LOGOUT_REDIRECT_URI=http://localhost:8080

9. Test the auth flow

Link to this section
  1. Start your server and go to http://localhost:8080/login. You should be redirected to the Kinde-hosted login page.

  2. Sign up with a test user account. After authenticating, Kinde redirects back to your callback URL, exchanges the code for tokens, and stores them in your session.

  3. You should land back on your app. Go to your Kinde dashboard > Users to confirm the test user was created.

Optional: Send to Kinde registration screen

Link to this section

By default, the user will be redirected to the login screen. To send users directly to the registration screen instead of the login screen do the following:

  1. Create a new flow instance with WithPrompt("create"):

    kindeRegisterFlow, err := authorization_code.NewAuthorizationCodeFlow(
    os.Getenv("KINDE_DOMAIN"),
    os.Getenv("KINDE_CLIENT_ID"),
    os.Getenv("KINDE_CLIENT_SECRET"),
    os.Getenv("KINDE_REDIRECT_URI"),
    authorization_code.WithSessionHooks(sessionHooks),
    authorization_code.WithOffline(),
    authorization_code.WithPrompt("create"),
    )
    if err != nil {
    log.Fatalf("failed to init register flow: %v", err)
    }
  2. Create a /register route:

    http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) {
    http.Redirect(w, r, kindeRegisterFlow.GetAuthURL(), http.StatusFound)
    })
  3. Send the user to the /register route.

Optional: PKCE flow (public clients)

Link to this section

For public clients, pass an empty string as the client secret and add WithPKCE():

authorization_code.WithPKCE() // SHA256 (S256) challenge method
authorization_code.WithPKCEChallengeMethod("plain") // custom challenge method

Device authorization flow

Link to this section

Use the device authorization flow for CLIs, TVs, and other devices with limited input.

1. Create a Kinde device app

Link to this section
  1. Go to your Kinde dashboard, select Add new application
  2. Set a Name and select the application type as Device and IoT
  3. Select Save
  4. Go to Details page and copy the Domain (or your custom domain) and Client ID values.
  5. Go to the Authentication page and select the authentication methods you want your users to sign in or sign up with (e.g., Google, Facebook, etc.).
  6. Select Save.

2. Import packages

Link to this section
  1. Import the packages for the device authorization flow:

    import (
    "github.com/kinde-oss/kinde-go/oauth2/authorization_code"
    "github.com/kinde-oss/kinde-go/jwt"
    "github.com/kinde-oss/kinde-go/frameworks/cli"
    )

3. Add environment variables

Link to this section
  1. Add your Kinde credentials to a .env file (or your environment config):

    KINDE_DOMAIN=https://YOUR_BUSINESS_DOMAIN.kinde.com
    KINDE_CLIENT_ID=<YOUR_CLIENT_ID>
  2. Load them at startup with a package like godotenv, or read them directly with os.Getenv. Replace the placeholder values with the credentials you copied from the Details page in step 1.

4. Initialize the device authorization flow

Link to this section
  1. For CLI and device apps, create a session that stores tokens in the OS secure keychain (Keychain on macOS, Credential Manager on Windows, Secret Service on Linux):

    cliSession, err := cli.NewCliSession("your-app-name")
    if err != nil {
    log.Fatalf("failed to init session: %v", err)
    }
  2. Initialize the device authorization flow:

    deviceFlow, err := authorization_code.NewDeviceAuthorizationFlow(
    os.Getenv("KINDE_DOMAIN"),
    authorization_code.WithClientID(os.Getenv("KINDE_CLIENT_ID")),
    authorization_code.WithSessionHooks(cliSession),
    authorization_code.WithOffline(),
    authorization_code.WithTokenValidation(
    true,
    jwt.WillValidateAlgorithm(),
    ),
    )
    if err != nil {
    log.Fatalf("failed to init device flow: %v", err)
    }

5. Request a device code

Link to this section
  1. Call StartDeviceAuth to start the flow and get the code to display to the user:

    da, err := deviceFlow.StartDeviceAuth(context.Background())
    if err != nil {
    log.Fatalf("failed to start device auth: %v", err)
    }
    fmt.Printf("Go to %s and enter code: %s\n", da.VerificationURI, da.UserCode)
  2. Instruct the user to go to the VerificationURI and enter the UserCode.

  3. The response also includes VerificationURIComplete — a single URL that pre-fills the user code, which you can use to generate a QR code for the user to scan on your device.

  4. Wait for the user to complete the authentication process on their other device.

6. Poll for the access token

Link to this section
  1. Call ExchangeDeviceAccessToken while the user authenticates on their other device. The SDK handles polling at the correct interval automatically and blocks until the user approves or the device code expires:

    err = deviceFlow.ExchangeDeviceAccessToken(context.Background(), da)
    if err != nil {
    log.Fatalf("token exchange failed: %v", err)
    }

7. Display user information

Link to this section
  1. Retrieve the token and access user information:

    token, err := deviceFlow.GetToken(context.Background())
    if err != nil {
    log.Fatalf("failed to get token: %v", err)
    }
    profile := token.GetUserProfile()
    userID := token.GetSubject()
  2. Or use an HTTP client to call a protected API — the access token is attached automatically:

    client, err := deviceFlow.GetClient(context.Background())
    if err != nil {
    log.Fatalf("failed to get client: %v", err)
    }
    resp, err := client.Get("https://your-api.com/protected-resource")
    if err != nil {
    log.Fatalf("request failed: %v", err)
    }
    defer resp.Body.Close()
  3. Display the user information and grant users protected resources.

Client credentials flow (M2M apps)

Link to this section

1. Create a Kinde M2M app

Link to this section
  1. Go to your Kinde dashboard, select Add new application
  2. Set a Name and select the application type as Machine to machine (M2M)
  3. Select Save
  4. Go to Details page and copy the Domain (or your custom domain), Client ID and Client secret values.
  5. Go to the APIs page and Authorize the APIs you want access from this M2M app. If you are using the Kinde Management API, you need to authorize the Management API.
  6. Enable the Scopes for the authorized APIs.

2. Import packages

Link to this section
  1. Import the packages for the client credentials flow:

    import (
    "github.com/kinde-oss/kinde-go/oauth2/client_credentials"
    "github.com/kinde-oss/kinde-go/jwt"
    "github.com/kinde-oss/kinde-go/kinde" // for Management API
    )

3. Add environment variables

Link to this section
  1. Add your Kinde credentials to a .env file (or your environment config):

    KINDE_DOMAIN=https://your-tenant.kinde.com
    KINDE_CLIENT_ID=your_client_id_here
    KINDE_CLIENT_SECRET=your_client_secret_here
  2. Load them at startup with a package like godotenv, or read them directly with os.Getenv. Replace the placeholder values with the credentials you copied from the Details page in step 1.

4. Initialize the client credentials flow

Link to this section
  1. Initialize the client credentials flow:

    kindeClient, err := client_credentials.NewClientCredentialsFlow(
    os.Getenv("KINDE_DOMAIN"),
    os.Getenv("KINDE_CLIENT_ID"),
    os.Getenv("KINDE_CLIENT_SECRET"),
    client_credentials.WithKindeManagementAPI(os.Getenv("KINDE_DOMAIN")), // adds the Management API audience
    client_credentials.WithTokenValidation(
    true,
    jwt.WillValidateAlgorithm(),
    ),
    )
    if err != nil {
    log.Fatalf("failed to init M2M client: %v", err)
    }

    WithKindeManagementAPI sets the audience required for Management API access. If you’re calling your own API instead, replace it with client_credentials.WithAudience("<your API audience>").

5. Get a token and call an API

Link to this section
  1. Use GetClient to retrieve an *http.Client pre-configured with a valid access token. The client fetches a new token automatically when it expires:

    client, err := kindeClient.GetClient(context.Background())
    if err != nil {
    log.Fatalf("failed to get client: %v", err)
    }
    resp, err := client.Get(os.Getenv("KINDE_DOMAIN") + "/api/v1/users")
    if err != nil {
    log.Fatalf("request failed: %v", err)
    }
    defer resp.Body.Close()
  2. To inspect the raw token directly — for example, to read claims or check expiry — use GetToken:

    token, err := kindeClient.GetToken(context.Background())
    if err != nil {
    log.Fatalf("failed to get token: %v", err)
    }
    accessToken, ok := token.GetAccessToken()

6. Use the Management API

Link to this section

The Management API lets you programmatically manage your Kinde tenant — users, organizations, applications, permissions, and more.

  1. Create the client from the initialized kindeClient:

    ctx := context.Background()
    managementApi, err := kinde.NewManagementAPI(ctx, os.Getenv("KINDE_DOMAIN"), kindeClient)
    if err != nil {
    log.Fatalf("failed to init Management API: %v", err)
    }

Example: list users

res, err := managementApi.GetUsers(ctx, nil)
if err != nil {
log.Fatalf("failed to get users: %v", err)
}

Example: create a user

res, err := managementApi.CreateUser(ctx, &management_api.CreateUserReq{
Profile: &management_api.CreateUserProfile{
GivenName: "Jane",
FamilyName: "Smith",
},
Identities: []management_api.CreateUserIdentity{
{
Type: management_api.CreateUserIdentityTypeEmail,
Details: management_api.CreateUserIdentityDetails{Email: "jane@example.com"},
},
},
})
if err != nil {
log.Fatalf("failed to create user: %v", err)
}

Example: create an application

res, err := managementApi.CreateApplication(ctx, &management_api.CreateApplicationReq{
Name: "Backend app",
Type: management_api.CreateApplicationReqTypeReg,
})
if err != nil {
// handle error
}

Handle responses:

Management API methods return an interface that may be one of several response types. Switch on the response to handle each case:

switch response := res.(type) {
case management_api.GetUsersBadRequest:
fmt.Printf("Bad request: %v\n", response)
case management_api.GetUsersForbidden:
fmt.Printf("Forbidden (missing scope): %v\n", response)
case management_api.GetUsersTooManyRequests:
fmt.Printf("Rate limited: %v\n", response)
case management_api.GetUsersResponse:
fmt.Printf("Users: %v\n", response)
default:
fmt.Printf("Unexpected response type: %T\n", response)
}

Learn more about the Kinde Management API here.

Session management

Link to this section

Both the authorization code and client credentials flows use session hooks to store and retrieve tokens. Provide an implementation of the SDK’s session hooks interface with WithSessionHooks(...). If none is provided, the client credentials flow defaults to an in-memory session (tokens are lost when the process terminates) - useful for testing and short-lived processes.

CLI session storage

Link to this section

For command-line tools and desktop apps, the SDK provides a pre-built session that stores tokens in the operating system’s secure keychain (Keychain on macOS, Credential Manager on Windows, Secret Service on Linux). It handles token chunking for large tokens automatically.

import "github.com/kinde-oss/kinde-go/frameworks/cli"
cliSession, err := cli.NewCliSession("your-app-name")
if err != nil {
// keychain / secret service not available
log.Fatalf("failed to init CLI session: %v", err)
}
kindeClient, err := client_credentials.NewClientCredentialsFlow(
"https://your-tenant.kinde.com",
"<client_id>",
"<client_secret>",
client_credentials.WithKindeManagementAPI("https://your-tenant.kinde.com"),
client_credentials.WithSessionHooks(cliSession),
)

Custom session storage

Link to this section

For production web apps, implement your own session storage backed by a database, Redis, encrypted files, or your existing session system.

The gin_kinde package provides a complete middleware that protects route groups, handles the callback automatically, and redirects unauthenticated users to sign in.

import "github.com/kinde-oss/kinde-go/frameworks/gin_kinde"
func main() {
router := gin.Default()
// Set up session middleware (e.g. gin-contrib/sessions)
privateGroup := router.Group("/")
gin_kinde.UseKindeAuth(privateGroup,
"https://your-tenant.kinde.com", // Kinde domain
"your-client-id",
"your-client-secret",
"http://localhost:8080", // base redirect URL
authorization_code.WithPrompt("login"),
authorization_code.WithOffline(),
)
privateGroup.GET("/profile", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "Authenticated!"})
})
router.Run(":8080")
}

UseKindeAuth accepts the same options as NewAuthorizationCodeFlow, so you can add WithAudience(...), WithPKCE(), WithTokenValidation(...), and others.

Custom middleware

Link to this section

For the standard library or other frameworks, check authentication and redirect as needed:

func AuthMiddleware(flow authorization_code.IAuthorizationCodeFlow) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ok, err := flow.IsAuthenticated(r.Context())
if err != nil || !ok {
http.Redirect(w, r, flow.GetAuthURL(), http.StatusFound)
return
}
next.ServeHTTP(w, r)
})
}
}

The jwt package parses and validates JWTs and provides convenient access to claims. Validation options are applied once during parsing and the results are cached on the token - subsequent reads (GetSubject(), GetAudience(), etc.) do not re-validate. When used inside the OAuth 2.0 flows, tokens are re-validated each time they are retrieved from the token source.

token, err := jwt.ParseFromString(
"your.jwt.token.here",
jwt.WillValidateWithJWKSUrl("https://your-tenant.kinde.com/.well-known/jwks.json"),
jwt.WillValidateAlgorithm("RS256"),
jwt.WillValidateAudience("your-api-audience"),
)
if err != nil {
// handle error
}
if token.IsValid() {
subject := token.GetSubject()
issuer := token.GetIssuer()
}

Parsing helpers:

MethodSource
ParseFromStringA raw token string.
ParseFromAuthorizationHeaderAn HTTP Authorization: Bearer <token> header.
ParseFromSessionStorageA JSON-serialized oauth2.Token from session storage.
ParseOAuth2TokenAn existing *oauth2.Token.

Validation options

Link to this section
OptionPurpose
WillValidateWithJWKSUrl(url)Validate the signature against a JWKS endpoint.
WillValidateWithPublicKey(fn)Validate the signature with a specific RSA public key.
WillValidateWithKeyFunc(fn)Provide a custom key function.
WillValidateAlgorithm(...algs)Restrict signing algorithms (defaults to RS256).
WillValidateAudience(aud)Require a specific audience.
WillValidateIssuer(iss)Require a specific issuer.
WillValidateClaims(fn)Run custom claim validation.
WillValidateWithClockSkew(d)Allow clock skew between servers.
WillValidateWithTimeFunc(fn)Supply a custom time function (useful for testing).

Access token information

Link to this section
isValid := token.IsValid()
subject := token.GetSubject()
issuer := token.GetIssuer()
audience := token.GetAudience()
orgCode := token.GetOrganizationCode()
claims := token.GetClaims()
accessToken, ok := token.GetAccessToken()
idToken, ok := token.GetIdToken()
refreshToken, ok := token.GetRefreshToken()
profile := token.GetUserProfile()
errs := token.GetValidationErrors()

Protect an API endpoint

Link to this section
func protectedHandler(w http.ResponseWriter, r *http.Request) {
token, err := jwt.ParseFromAuthorizationHeader(
r,
jwt.WillValidateWithJWKSUrl("https://your-tenant.kinde.com/.well-known/jwks.json"),
jwt.WillValidateAlgorithm("RS256"),
jwt.WillValidateAudience("your-api-audience"),
)
if err != nil || !token.IsValid() {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// token is valid
subject := token.GetSubject()
_ = subject
}

Access management

Link to this section

The account_api client backs the API-aware token helpers in the sections below. Construct it with a base URL and a function that returns a current access token - for example, one sourced from a flow’s GetToken:

import "github.com/kinde-oss/kinde-go/kinde/account_api"
accountClient, err := account_api.NewClient(
"<YOUR_DOMAIN>", // kinde issuer URL or custom domain
func(ctx context.Context) (string, error) {
token, err := kindeClient.GetToken(ctx)
if err != nil {
return "", err
}
accessToken, _ := token.GetAccessToken()
return accessToken, nil
},
)

Read permissions directly from the token (see Define user permissions for how they’re defined):

permissions := token.GetPermissions()

The token also exposes API-aware helpers that fall back to the Kinde Account API when a value isn’t present in the token. These take a *account_api.Client:

access, err := token.GetPermission(ctx, accountClient, "create:todos", jwt.GetPermissionOptions{})
ok, err := token.HasPermissions(ctx, accountClient, []string{"create:todos", "read:todos"}, jwt.HasPermissionsOptions{})
roles := token.GetRoles()
hasRoles := token.HasRoles("admin", "editor")
// API-aware variants
roles, err := token.GetRolesWithAPI(ctx, accountClient, false)
ok, err := token.HasRolesWithAPI(ctx, accountClient, []string{"admin"}, jwt.HasRolesOptions{})

Read feature flags from the token, with type-specific accessors:

flags := token.GetFeatureFlags()
flag, ok := token.GetFeatureFlag("theme")
boolVal, ok := token.GetFeatureFlagBool("is_dark_mode")
strVal, ok := token.GetFeatureFlagString("theme")
intVal, ok := token.GetFeatureFlagInt("competitions_limit")
// API-aware variants
flagValue, err := token.GetFlag(ctx, accountClient, "theme", false)
hasFlags, err := token.HasFeatureFlags(ctx, accountClient, []string{"theme"}, jwt.HasFeatureFlagsOptions{})

Entitlements describe what a customer is allowed to use based on their billing plan. These helpers read from the Kinde Account API:

result, err := token.GetEntitlements(ctx, accountClient)
entitlement, err := token.GetEntitlement(ctx, accountClient, "seats")
ok, err := token.HasBillingEntitlements(ctx, accountClient, []string{"pro_plan"}, jwt.HasBillingEntitlementsOptions{})

API Reference - Authorization Code Flow

Link to this section

Create the instance

Link to this section
kindeAuthFlow, err := authorization_code.NewAuthorizationCodeFlow(
"<YOUR_DOMAIN>", // kinde issuer URL or custom domain
"<client_id>",
"<client_secret>",
"http://localhost:8080/callback", // application callback URL
authorization_code.WithSessionHooks(sessionHooks), // your ISessionHooks implementation
authorization_code.WithOffline(), // adds offline scope and manages refresh tokens
authorization_code.WithAudience("<your API audience>"),
authorization_code.WithTokenValidation(
true, // validate signature via JWKS
jwt.WillValidateAlgorithm(), // validate alg is RS256
jwt.WillValidateAudience("<your API audience>"),
),
)

Returns the full authorization URL to redirect the user to in order to start authentication. Includes a CSRF-safe state parameter generated automatically.

Params:

GetAuthURL()

Usage:

http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, kindeAuthFlow.GetAuthURL(), http.StatusFound)
})

Output: string


GetAuthURLWithInvitation

Link to this section

Returns the full authorization URL, with optional invitation code support. If invitationCode is provided, it appends invitation_code and is_invitation=true to the URL, directing the user into a pre-filled invitation flow.

Params:

GetAuthURLWithInvitation(invitationCode string)

  • invitationCode — invitation code to pre-fill; pass an empty string to behave identically to GetAuthURL()

Usage:

http.HandleFunc("/invite", func(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("invitation_code")
http.Redirect(w, r, kindeAuthFlow.GetAuthURLWithInvitation(code), http.StatusFound)
})

Output: string


Exchanges the authorization code returned by Kinde for an access token. Validates the state parameter to prevent CSRF attacks, then stores the token using your session hooks.

Params:

ExchangeCode(ctx context.Context, authorizationCode string, receivedState string)

  • ctx — request context
  • authorizationCode — the code query parameter from the Kinde callback URL
  • receivedState — the state query parameter from the Kinde callback URL; validated against the stored state to prevent CSRF

Usage:

err := kindeAuthFlow.ExchangeCode(r.Context(), r.URL.Query().Get("code"), r.URL.Query().Get("state"))
if err != nil {
http.Error(w, "token exchange failed", http.StatusInternalServerError)
return
}

Output: error


AuthorizationCodeReceivedHandler

Link to this section

A ready-made HTTP handler for your callback route. Internally calls ExchangeCode and handles error responses. Use this when you don’t need custom logic in the callback.

Params:

AuthorizationCodeReceivedHandler(w http.ResponseWriter, r *http.Request)

  • w — HTTP response writer
  • r — incoming HTTP request containing the code and state query parameters

Usage:

http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
kindeAuthFlow.AuthorizationCodeReceivedHandler(w, r)
})

Output: none — writes the HTTP response directly.


IsAuthenticated

Link to this section

Checks whether a valid, non-expired token exists in session storage for the current request context.

Params:

IsAuthenticated(ctx context.Context)

  • ctx — request context

Usage:

ok, err := kindeAuthFlow.IsAuthenticated(r.Context())
if err != nil || !ok {
http.Redirect(w, r, kindeAuthFlow.GetAuthURL(), http.StatusFound)
return
}

Output: (bool, error)


Returns the current session’s parsed *jwt.Token. Reads from session storage and refreshes the token if it has expired (requires WithOffline()).

Params:

GetToken(ctx context.Context)

  • ctx — request context

Usage:

token, err := kindeAuthFlow.GetToken(r.Context())
if err != nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
subject := token.GetSubject()
profile := token.GetUserProfile()

Output: (*jwt.Token, error)


Returns an *http.Client pre-configured with the user’s access token. If WithOffline() was set and the token has expired, it automatically refreshes before returning the client.

Params:

GetClient(ctx context.Context)

  • ctx — request context

Usage:

client, err := kindeAuthFlow.GetClient(r.Context())
if err != nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
resp, err := client.Get("https://api.example.com/data")

Output: (*http.Client, error)


Clears the locally stored tokens from session storage. Does not end the session on Kinde’s side — redirect the user to your allowed logout redirect URL to complete sign-out fully.

Params:

Logout()

Usage:

http.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
if err := kindeAuthFlow.Logout(); err != nil {
http.Error(w, "logout failed", http.StatusInternalServerError)
return
}
http.Redirect(w, r, os.Getenv("KINDE_LOGOUT_REDIRECT_URI"), http.StatusFound)
})

Output: error


InjectTokenMiddleware

Link to this section

HTTP middleware that reads the current token from session storage and injects it into the request context, making it available to downstream handlers. Pair with TokenFromContext to read the token in a handler.

Params:

InjectTokenMiddleware(next http.Handler)

  • next — the downstream http.Handler to call after injecting the token into the request context

Usage:

mux := http.NewServeMux()
mux.HandleFunc("/protected", protectedHandler)
http.ListenAndServe(":8080", kindeAuthFlow.InjectTokenMiddleware(mux))

Output: http.Handler


TokenFromContext

Link to this section

Package-level helper that retrieves the token previously injected by InjectTokenMiddleware from the request context. Returns nil, false if no token is present.

Params:

authorization_code.TokenFromContext(ctx context.Context)

  • ctx — the request context, typically r.Context()

Usage:

func protectedHandler(w http.ResponseWriter, r *http.Request) {
token, ok := authorization_code.TokenFromContext(r.Context())
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
userID := token.GetSubject()
w.Write([]byte("Hello, " + userID))
}

Output: (*jwt.Token, bool)


API Reference - Device Authorization Flow

Link to this section

Create the instance

Link to this section
deviceFlow, err := authorization_code.NewDeviceAuthorizationFlow(
"<YOUR_DOMAIN>", // kinde issuer URL or custom domain
authorization_code.WithClientID("<YOUR_CLIENT_ID>"), // application client ID
authorization_code.WithSessionHooks(cliSession), // your session implementation
authorization_code.WithOffline(), // adds offline scope and manages refresh tokens
authorization_code.WithTokenValidation(
true,
jwt.WillValidateAlgorithm(),
),
)

StartDeviceAuth

Link to this section

Starts the device authorization flow. Returns a DeviceAuthResponse containing the user_code and verification_uri to display to the user, plus the device_code used to poll for a token.

Params:

StartDeviceAuth(ctx context.Context)

  • ctx — request context

Usage:

da, err := deviceFlow.StartDeviceAuth(context.Background())
if err != nil {
log.Fatalf("failed to start device auth: %v", err)
}
fmt.Printf("Go to %s and enter code: %s\n", da.VerificationURI, da.UserCode)

Output: (*oauth2.DeviceAuthResponse, error)


ExchangeDeviceAccessToken

Link to this section

Polls Kinde until the user completes authorization on their other device, then exchanges the device code for an access token and stores it using your session hooks.

Params:

ExchangeDeviceAccessToken(ctx context.Context, da *oauth2.DeviceAuthResponse, opts ...oauth2.AuthCodeOption)

  • ctx — request context
  • da — the *oauth2.DeviceAuthResponse returned by StartDeviceAuth
  • opts — optional additional oauth2.AuthCodeOption values (variadic)

Usage:

err := deviceFlow.ExchangeDeviceAccessToken(context.Background(), da)
if err != nil {
log.Fatalf("token exchange failed: %v", err)
}

Output: error


Returns the current session’s parsed *jwt.Token. Reads from session storage and refreshes the token if it has expired (requires WithOffline()).

Params:

GetToken(ctx context.Context)

  • ctx — request context

Usage:

token, err := deviceFlow.GetToken(context.Background())
if err != nil {
log.Fatalf("failed to get token: %v", err)
}
subject := token.GetSubject()

Output: (*jwt.Token, error)


Returns an *http.Client pre-configured with the device flow’s access token. Refreshes automatically when the token expires if WithOffline() was set.

Params:

GetClient(ctx context.Context)

  • ctx — request context

Usage:

client, err := deviceFlow.GetClient(context.Background())
if err != nil {
log.Fatalf("failed to get client: %v", err)
}
resp, err := client.Get("https://api.example.com/data")

Output: (*http.Client, error)


IsAuthenticated

Link to this section

Checks whether a valid, non-expired token is present in session storage.

Params:

IsAuthenticated(ctx context.Context)

  • ctx — request context

Usage:

ok, err := deviceFlow.IsAuthenticated(context.Background())
if err != nil || !ok {
log.Println("not authenticated")
}

Output: (bool, error)


Clears the locally stored tokens from session storage.

Params:

Logout()

Usage:

err := deviceFlow.Logout()

Output: error


API Reference - Client Credentials Flow

Link to this section

Create the instance

Link to this section
kindeClient, err := client_credentials.NewClientCredentialsFlow(
"<YOUR_DOMAIN>", // kinde issuer URL or custom domain
"<YOUR_CLIENT_ID>", // application client ID
"<YOUR_CLIENT_SECRET>", // application client secret
// Use ONE of the following — they are mutually exclusive:
client_credentials.WithKindeManagementAPI("<YOUR_DOMAIN>"), // for Kinde Management API access
// client_credentials.WithAudience("<YOUR_API_AUDIENCE>"), // for your own API instead
client_credentials.WithTokenValidation(true, jwt.WillValidateAlgorithm()),
)

Returns an *http.Client pre-configured with a valid access token. Reads the token from session storage if present, fetches a new one if missing, and re-fetches automatically as it expires.

Params:

GetClient(ctx context.Context)

  • ctx — request context

Usage:

client, err := kindeClient.GetClient(context.Background())
if err != nil {
log.Fatalf("failed to init client: %v", err)
}
resp, err := client.Get("https://api.example.com/resource")

Output: (*http.Client, error)


Returns the raw *jwt.Token for the current M2M session. Reads from session storage and fetches a new token when expired.

Params:

GetToken(ctx context.Context)

  • ctx — request context

Usage:

token, err := kindeClient.GetToken(context.Background())
if err != nil {
log.Fatalf("failed to get token: %v", err)
}
accessToken, ok := token.GetAccessToken()

Output: (*jwt.Token, error)