Documentation
¶
Overview ¶
Example ¶
Example demonstrates basic usage of TokenManager with gRPC interceptors.
package main
import (
"context"
"fmt"
"log"
"net"
"sync"
"github.com/AmmannChristian/go-authx/oauth2client"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/test/bufconn"
)
const bufSize = 1024
var (
bufListener = bufconn.Listen(bufSize)
bufServer = grpc.NewServer()
bufOnce sync.Once
)
func startBufServer() {
bufOnce.Do(func() {
go func() {
_ = bufServer.Serve(bufListener)
}()
})
}
func dialBufConn(opts ...grpc.DialOption) (*grpc.ClientConn, error) {
startBufServer()
dialOpts := []grpc.DialOption{
grpc.WithContextDialer(func(c context.Context, _ string) (net.Conn, error) {
select {
case <-c.Done():
return nil, c.Err()
default:
}
return bufListener.Dial()
}),
grpc.WithTransportCredentials(insecure.NewCredentials()),
}
dialOpts = append(dialOpts, opts...)
return grpc.NewClient("bufnet", dialOpts...)
}
func main() {
ctx := context.Background()
// Create token manager
tm := oauth2client.NewTokenManager(
ctx,
"https://auth.example.com/oauth/v2/token",
"client-id",
"client-secret",
"openid profile email",
)
// Use with gRPC client
conn, err := dialBufConn(
grpc.WithUnaryInterceptor(tm.UnaryClientInterceptor()),
grpc.WithStreamInterceptor(tm.StreamClientInterceptor()),
)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
fmt.Println("gRPC client configured with OAuth2 authentication")
}
Output: gRPC client configured with OAuth2 authentication
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Logger ¶
Logger is an interface for optional logging in TokenManager. Implementations can log token refresh events if desired.
type Option ¶
type Option func(*TokenManager)
Option is a functional option for configuring TokenManager.
func WithLogger ¶
WithLogger sets a custom logger for token refresh events. If not set, no logging will occur.
func WithLoggingEnabled ¶
func WithLoggingEnabled() Option
WithLoggingEnabled enables logging using the default Go log package. This is a convenience option that sets the logger to log.Default().
type TokenManager ¶
type TokenManager struct {
// contains filtered or unexported fields
}
TokenManager manages OAuth2 tokens with automatic refresh. It uses the client credentials flow and is safe for concurrent access.
func NewTokenManager ¶
func NewTokenManager(ctx context.Context, tokenURL, clientID, clientSecret, scopes string, opts ...Option) *TokenManager
NewTokenManager creates a new OAuth2 token manager using client credentials flow.
Parameters:
- ctx: Context for token requests (used as fallback for backward compatibility)
- tokenURL: OAuth2 token endpoint (e.g., "https://auth.example.com/oauth/v2/token")
- clientID: OAuth2 client identifier
- clientSecret: OAuth2 client secret
- scopes: Space-separated list of OAuth2 scopes (e.g., "openid profile email")
- opts: Optional configuration options (WithLogger, WithLoggingEnabled)
Example ¶
ExampleNewTokenManager demonstrates creating a new token manager.
package main
import (
"context"
"fmt"
"github.com/AmmannChristian/go-authx/oauth2client"
)
func main() {
ctx := context.Background()
tm := oauth2client.NewTokenManager(
ctx,
"https://auth.example.com/oauth/v2/token",
"my-client-id",
"my-client-secret",
"openid profile",
)
fmt.Printf("TokenManager created for client: %s\n", "my-client-id")
_ = tm // Use the token manager
}
Output: TokenManager created for client: my-client-id
func (*TokenManager) GetToken
deprecated
func (tm *TokenManager) GetToken() (string, error)
GetToken returns a valid access token, fetching or refreshing if necessary. This method is thread-safe and uses double-checked locking to minimize lock contention.
Deprecated: Use GetTokenWithContext instead to properly handle context cancellation and deadlines. This method uses a background context and cannot be cancelled by the caller.
Returns:
- string: Valid access token
- error: Error if token fetch/refresh fails
Example ¶
ExampleTokenManager_GetToken demonstrates manual token retrieval.
package main
import (
"context"
"fmt"
"github.com/AmmannChristian/go-authx/oauth2client"
)
func main() {
ctx := context.Background()
tm := oauth2client.NewTokenManager(
ctx,
"https://auth.example.com/oauth/v2/token",
"client-id",
"client-secret",
"openid",
)
// This would normally fetch a real token
// For demonstration purposes, we just show the pattern
_, err := tm.GetToken()
if err != nil {
// Handle error (in production this would connect to real OAuth2 server)
fmt.Println("Token fetch attempted")
}
}
Output: Token fetch attempted
func (*TokenManager) GetTokenWithContext ¶
func (tm *TokenManager) GetTokenWithContext(ctx context.Context) (string, error)
GetTokenWithContext returns a valid access token, fetching or refreshing if necessary. This method respects the provided context's cancellation and deadline. This method is thread-safe and uses double-checked locking to minimize lock contention.
Parameters:
- ctx: Context for the token request (used for cancellation and deadlines)
Returns:
- string: Valid access token
- error: Error if token fetch/refresh fails or context is cancelled
func (*TokenManager) StreamClientInterceptor ¶
func (tm *TokenManager) StreamClientInterceptor() grpc.StreamClientInterceptor
StreamClientInterceptor returns a gRPC stream client interceptor that automatically adds OAuth2 Bearer tokens to request metadata.
The interceptor adds the token as "authorization: Bearer <token>" to the outgoing request context metadata. If token fetch fails, stream creation is aborted with an error. The interceptor respects the RPC context's cancellation and deadline.
Usage:
conn, err := grpc.NewClient(
"server:9090",
grpc.WithStreamInterceptor(tokenManager.StreamClientInterceptor()),
)
Example ¶
ExampleTokenManager_StreamClientInterceptor demonstrates using the stream interceptor.
package main
import (
"context"
"fmt"
"log"
"net"
"sync"
"github.com/AmmannChristian/go-authx/oauth2client"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/test/bufconn"
)
const bufSize = 1024
var (
bufListener = bufconn.Listen(bufSize)
bufServer = grpc.NewServer()
bufOnce sync.Once
)
func startBufServer() {
bufOnce.Do(func() {
go func() {
_ = bufServer.Serve(bufListener)
}()
})
}
func dialBufConn(opts ...grpc.DialOption) (*grpc.ClientConn, error) {
startBufServer()
dialOpts := []grpc.DialOption{
grpc.WithContextDialer(func(c context.Context, _ string) (net.Conn, error) {
select {
case <-c.Done():
return nil, c.Err()
default:
}
return bufListener.Dial()
}),
grpc.WithTransportCredentials(insecure.NewCredentials()),
}
dialOpts = append(dialOpts, opts...)
return grpc.NewClient("bufnet", dialOpts...)
}
func main() {
ctx := context.Background()
tm := oauth2client.NewTokenManager(
ctx,
"https://auth.example.com/oauth/v2/token",
"client-id",
"client-secret",
"openid",
)
// Create gRPC connection with stream interceptor
conn, err := dialBufConn(
grpc.WithStreamInterceptor(tm.StreamClientInterceptor()),
)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
fmt.Println("Stream interceptor configured")
}
Output: Stream interceptor configured
func (*TokenManager) UnaryClientInterceptor ¶
func (tm *TokenManager) UnaryClientInterceptor() grpc.UnaryClientInterceptor
UnaryClientInterceptor returns a gRPC unary client interceptor that automatically adds OAuth2 Bearer tokens to request metadata.
The interceptor adds the token as "authorization: Bearer <token>" to the outgoing request context metadata. If token fetch fails, the RPC call is aborted with an error. The interceptor respects the RPC context's cancellation and deadline.
Usage:
conn, err := grpc.NewClient(
"server:9090",
grpc.WithUnaryInterceptor(tokenManager.UnaryClientInterceptor()),
)
Example ¶
ExampleTokenManager_UnaryClientInterceptor demonstrates using the unary interceptor.
package main
import (
"context"
"fmt"
"log"
"net"
"sync"
"github.com/AmmannChristian/go-authx/oauth2client"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/test/bufconn"
)
const bufSize = 1024
var (
bufListener = bufconn.Listen(bufSize)
bufServer = grpc.NewServer()
bufOnce sync.Once
)
func startBufServer() {
bufOnce.Do(func() {
go func() {
_ = bufServer.Serve(bufListener)
}()
})
}
func dialBufConn(opts ...grpc.DialOption) (*grpc.ClientConn, error) {
startBufServer()
dialOpts := []grpc.DialOption{
grpc.WithContextDialer(func(c context.Context, _ string) (net.Conn, error) {
select {
case <-c.Done():
return nil, c.Err()
default:
}
return bufListener.Dial()
}),
grpc.WithTransportCredentials(insecure.NewCredentials()),
}
dialOpts = append(dialOpts, opts...)
return grpc.NewClient("bufnet", dialOpts...)
}
func main() {
ctx := context.Background()
tm := oauth2client.NewTokenManager(
ctx,
"https://auth.example.com/oauth/v2/token",
"client-id",
"client-secret",
"openid",
)
// Create gRPC connection with unary interceptor
conn, err := dialBufConn(
grpc.WithUnaryInterceptor(tm.UnaryClientInterceptor()),
)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
fmt.Println("Unary interceptor configured")
}
Output: Unary interceptor configured