OAuth 2.0 client SDK for Go applications connecting to a PQAuth authorization server. Handles token acquisition, automatic refresh, PKCE code generation, and server discovery — so you can focus on your application logic.
- All major grant types — Authorization Code (PKCE), Client Credentials, Refresh Token, Device Authorization (RFC 8628), JWT Bearer (RFC 7523)
- Automatic token refresh — transparent refresh before expiry
- Server discovery — populates all endpoints automatically from RFC 8414 / OIDC Discovery metadata
- Thread-safe — safe to share a single
PQAuthClientacross goroutines - Standard PKCE — S256 by default; plain supported
- Advanced flows — PAR (RFC 9126), Token Introspection (RFC 7662), Token Revocation (RFC 7009)
go get github.com/PQAuth/pqauth-clientpackage main
import (
"context"
"fmt"
"log"
pqclient "github.com/PQAuth/pqauth-client"
)
func main() {
ctx := context.Background()
client, err := pqclient.NewWithDiscovery(ctx,
"https://auth.example.com",
"my-service",
pqclient.WithClientSecret("my-secret"),
pqclient.WithDefaultScopes("api:read", "api:write"),
)
if err != nil {
log.Fatal(err)
}
tokenSet, err := client.ClientCredentials(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println("Access token:", tokenSet.AccessToken)
fmt.Println("Expires at:", tokenSet.ExpiresAt)
}// Step 1 — generate the authorization URL
authURL, codeVerifier, err := client.AuthorizationCodeStart(
ctx,
"https://app.example.com/callback",
pqclient.WithCallScopes("openid", "profile"),
)
// Redirect the user to authURL
// Step 2 — exchange the code after the redirect
tokenSet, err := client.AuthorizationCodeExchange(
ctx,
code, // from query parameter
codeVerifier, // saved from step 1
"https://app.example.com/callback",
)// Token() returns the current token and refreshes it transparently if it is close to expiry.
tokenSet, err := client.Token(ctx)tokenSet, err := client.DeviceAuthorizationFlow(ctx,
pqclient.WithCallScopes("openid", "profile"),
)
// The SDK handles polling internally until the user completes the flow.// Recommended: auto-discover endpoints from the server's metadata document
client, err := pqclient.NewWithDiscovery(ctx, serverURL, clientID, opts...)
// Manual: specify each endpoint directly
client, err := pqclient.New(
pqclient.WithServerURL("https://auth.example.com"),
pqclient.WithClientID("my-app"),
pqclient.WithClientSecret("secret"),
pqclient.WithTokenEndpoint("https://auth.example.com/oauth/token"),
)// Client identity
pqclient.WithClientID("my-app")
pqclient.WithClientSecret("secret")
pqclient.WithClientAuthMethod("client_secret_basic") // or "client_secret_post", "private_key_jwt"
// Default scopes used when no per-call scopes are provided
pqclient.WithDefaultScopes("openid", "profile", "api:read")
// Automatic token refresh
pqclient.WithAutoRefresh(true)
pqclient.WithRefreshThreshold(30 * time.Second) // refresh when less than 30s remain
// HTTP transport
pqclient.WithHTTPClient(myHTTPClient)
pqclient.WithRequestTimeout(10 * time.Second)
pqclient.WithMaxRetries(3)
pqclient.WithRetryBackoff(500 * time.Millisecond)
// JWT Bearer assertions (RFC 7523)
pqclient.WithAssertionLifetime(5 * time.Minute)
// Device flow polling
pqclient.WithDevicePollInterval(5 * time.Second)
// Endpoint overrides (when not using discovery)
pqclient.WithTokenEndpoint("...")
pqclient.WithDeviceEndpoint("...")
pqclient.WithIntrospectionEndpoint("...")
pqclient.WithRevocationEndpoint("...")
pqclient.WithUserInfoEndpoint("...")
pqclient.WithPAREndpoint("...")Override defaults for individual requests:
tokenSet, err := client.ClientCredentials(ctx,
pqclient.WithCallScopes("reports:read"),
)
authURL, verifier, err := client.AuthorizationCodeStart(ctx, redirectURI,
pqclient.WithCallScopes("openid", "profile", "billing:read"),
)
tokenSet, err := client.JWTBearerGrant(ctx,
pqclient.WithCallSubject("user-456"),
pqclient.WithCallAudience("https://api.example.com"),
pqclient.WithCallExtraClaims(map[string]interface{}{"tenant": "acme"}),
)type TokenSet struct {
AccessToken string
TokenType string
RefreshToken string
Scope string
ExpiresAt time.Time
IssuedAt time.Time
Raw map[string]interface{} // full token response
}
func (t *TokenSet) IsExpired() bool
func (t *TokenSet) HasRefreshToken() bool
func (t *TokenSet) TimeUntilExpiry() time.DurationThe client uses S256 by default. You can also use the PKCE helpers directly:
import "github.com/PQAuth/pqauth-client/pkce"
pair, err := pkce.Generate(pkce.MethodS256)
fmt.Println(pair.Verifier)
fmt.Println(pair.Challenge)
fmt.Println(pair.Method) // "S256"// Introspect any token (RFC 7662)
result, err := client.Introspect(ctx, accessToken)
fmt.Println(result.Active, result.Subject)
// Revoke a token (RFC 7009)
err = client.Revoke(ctx, refreshToken)userInfo, err := client.UserInfo(ctx)
fmt.Println(userInfo.Subject, userInfo.Email)par, err := client.PushAuthorizationRequest(ctx, params)
// Redirect the user to par.RequestURImeta := client.Discovery()
fmt.Println(meta.TokenEndpoint)
fmt.Println(meta.SupportedGrantTypes)
fmt.Println(meta.SupportedCodeChallengeMethods)| Module | Description |
|---|---|
pqauth-core |
Cryptographic primitives and JWT utilities |
pqauth-auth-svr |
OAuth 2.0 Authorization Server |
pqauth-resource-svr |
Resource server token validation |
pqauth-svr-core |
Database and cache abstractions |
MIT