auth

package
v0.1.23 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 2, 2026 License: MIT, MIT Imports: 9 Imported by: 0

README

auth

auth is a Go library providing robust authentication and authorization functionalities for HTTP services. It includes JWT validation, role and permission-based authorization, and automatic key and token refresh mechanisms. Additionally, the library provides middleware to make it easy for your services to make authenticated requests to other services, simplifying secure inter-service communication in distributed systems.

Table of Contents

Features

  • JWT Authentication: Validate JSON Web Tokens (JWT) with signature verification.
  • Role and Permission-Based Authorization: Fine-grained access control using roles and permissions.
  • Automatic Refresh Mechanisms: Periodic refresh of authentication keys and access tokens.
  • Custom Authentication Providers: Support for custom logic via the AuthProvider interface.
  • Helper Functions: Utilities for token extraction and authorization requirement definitions.
  • HTTP Client Middleware: HTTP client that automatically injects access tokens into requests.

Installation

go get github.com/albeebe/service/pkg/auth

Getting Started

Implementing AuthProvider

To use the library, you need to implement the AuthProvider interface, which defines methods for authorization checks and token/key refresh logic.

type MyAuthProvider struct {
    // Fields for your provider
}

func (p *MyAuthProvider) AuthorizeRequest(r *http.Request, authRequirements auth.AuthRequirements) (isAuthorized bool, err error) {
    // Implement your authorization logic
    return true, nil
}

func (p *MyAuthProvider) IsServiceRequest(r *http.Request) (isService bool) {
    // Determine if the request is from a service
    return false
}

func (p *MyAuthProvider) RefreshAccessToken() (accessToken *auth.AccessToken, nextRefresh time.Time, err error) {
    // Refresh and return a new access token
    return &auth.AccessToken{
        Token:   "new-access-token",
        Expires: time.Now().Add(1 * time.Hour),
    }, time.Now().Add(55 * time.Minute), nil
}

func (p *MyAuthProvider) RefreshKeys() (keys []*auth.Key, nextRefresh time.Time, err error) {
    // Fetch and return new authentication keys
    keys := []*auth.Key{
        {
            Kid: "key-id",
            Iat: time.Now().Unix(),
            Exp: time.Now().Add(24 * time.Hour).Unix(),
            Alg: "RS256",
            Pem: "public-key-in-pem-format",
        },
    }
    return keys, time.Now().Add(12 * time.Hour), nil
}
Initializing Auth

Create a new Auth instance by providing a context and configuration with your AuthProvider.

ctx := context.Background()
authProvider := &MyAuthProvider{}

config := auth.Config{
    AuthProvider: authProvider,
}

authInstance, err := auth.New(ctx, config)
if err != nil {
    log.Fatalf("Failed to initialize auth: %v", err)
}
Starting the Auth Service

Start the auth service to initialize periodic refresh routines for keys and tokens.

errorChan := authInstance.Start()

// Handle errors from the error channel
go func() {
    for err := range errorChan {
        log.Printf("Auth error: %v", err)
    }
}()

Usage

Authenticating Requests

Use the Authenticate method to validate incoming HTTP requests by checking the bearer token in the Authorization header.

func myHandler(w http.ResponseWriter, r *http.Request) {
    authenticated, reason, err := authInstance.Authenticate(r)
    if err != nil {
        http.Error(w, "Internal Server Error", http.StatusInternalServerError)
        return
    }
    if !authenticated {
        http.Error(w, reason, http.StatusUnauthorized)
        return
    }
    // Proceed with handling the authenticated request
}
Authorizing Requests

Use the Authorize method to check if the authenticated request has the required roles or permissions.

authRequirements := auth.AuthRequirements{
    AnyRole:        []string{"admin", "editor"},
    AllPermissions: []string{"read", "write"},
}

authorized, err := authInstance.Authorize(r, authRequirements)
if err != nil {
    http.Error(w, "Internal Server Error", http.StatusInternalServerError)
    return
}
if !authorized {
    http.Error(w, "Forbidden", http.StatusForbidden)
    return
}
// Proceed with handling the authorized request

Alternatively, use helper functions to define requirements:

authorized, err := authInstance.Authorize(r, auth.AnyRole("admin", "editor"))
Creating an Authenticated HTTP Client

Create an HTTP client that automatically injects the access token into requests using NewAuthClient.

httpClient, err := authInstance.NewAuthClient()
if err != nil {
    log.Fatalf("Failed to create auth client: %v", err)
}

resp, err := httpClient.Get("https://api.example.com/protected-resource")
if err != nil {
    log.Fatalf("Request failed: %v", err)
}
defer resp.Body.Close()
// Process the response
Extracting Bearer Tokens

Extract the bearer token from an HTTP request using ExtractBearerToken.

token, ok := auth.ExtractBearerToken(r)
if !ok {
    http.Error(w, "Unauthorized", http.StatusUnauthorized)
    return
}
// Use the token as needed

Types and Interfaces

AuthProvider Interface

Defines methods that custom authentication providers must implement.

type AuthProvider interface {
    // AuthorizeRequest checks if the request meets the specified authorization requirements.
    // It returns true if the request is authorized, otherwise false, and an error if something goes wrong.
    AuthorizeRequest(r *http.Request, authRequirements AuthRequirements) (isAuthorized bool, err error)

    // IsServiceRequest checks whether the given HTTP request originates from a service.
    // It returns true if the request is identified as a service request, otherwise false.
    IsServiceRequest(r *http.Request) (isService bool)

    // RefreshAccessToken refreshes the current access token.
    // It returns the new access token, the time for the next refresh, and an error if the operation fails.
    RefreshAccessToken() (accessToken *AccessToken, nextRefresh time.Time, err error)

    // RefreshKeys retrieves updated authentication keys and the scheduled time for the next key refresh.
    // It returns a slice of keys, the time for the next refresh, and an error if the operation fails.
    RefreshKeys() (keys []*Key, nextRefresh time.Time, err error)}

AuthRequirements

Specifies roles and permissions required for authorization.

type AuthRequirements struct {
    AnyRole        []string // At least one role must be present
    AllPermissions []string // All permissions must be granted
}
Helper Functions
  • auth.AnyRole(roles ...string) AuthRequirements: Requires at least one of the specified roles.
  • auth.AllPermissions(permissions ...string) AuthRequirements: Requires all specified permissions.
AccessToken

Represents an access token with its expiration time.

type AccessToken struct {
    Token   string    // The access token string
    Expires time.Time // The token's expiration time
}
Key

Represents an authentication key used for token verification.

type Key struct {
    Kid string // Unique identifier for the key
    Iat int64  // Issued-at time (Unix timestamp)
    Exp int64  // Expiration time (Unix timestamp)
    Alg string // Algorithm used (e.g., "RS256")
    Pem string // RSA public key in PEM format
}

Error Handling

The Start method returns an error channel that reports issues encountered during refresh operations. It's important to listen to this channel to handle errors appropriately.

errorChan := authInstance.Start()

go func() {
    for err := range errorChan {
        // Handle errors such as logging or retry mechanisms
        log.Printf("Auth error: %v", err)
    }
}()

When using Authenticate and Authorize, check for errors and handle unauthorized or forbidden responses accordingly.

License

This project is licensed under the MIT License. See the LICENSE file for details.

Contributing

Contributions are welcome! Feel free to open an issue or submit a pull request with any proposed changes.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ExtractBearerToken

func ExtractBearerToken(r *http.Request) (string, bool)

ExtractBearerToken extracts the Bearer token from the Authorization header of an HTTP request. It returns the token and a boolean indicating whether the token was successfully extracted.

Types

type AccessToken

type AccessToken struct {
	Token   string    // The access token string
	Expires time.Time // The token's expiration time
}

type Auth

type Auth struct {
	// contains filtered or unexported fields
}

func New

func New(ctx context.Context, config Config) (*Auth, error)

New initializes and returns a new Auth instance after validating the config.

func (*Auth) Authenticate

func (a *Auth) Authenticate(r *http.Request) (authenticated bool, reason string, err error)

Authenticate checks the provided HTTP request for a valid Bearer token in the Authorization header. If the token is missing, malformed, or invalid, it returns false, a reason, and an error. The reason is only set when the request cannot be authenticated, and it is designed to be sent back to the client to provide feedback on why authentication failed.

func (*Auth) Authorize

func (a *Auth) Authorize(r *http.Request, permission string) (authorized bool, err error)

Authorize checks if the request meets the given permission. Returns true if authorized, false otherwise, and an error if the request is invalid or the check fails.

func (*Auth) IsServiceRequest

func (a *Auth) IsServiceRequest(r *http.Request) bool

IsServiceRequest checks whether the given HTTP request originates from a service. It delegates the request to the underlying AuthProvider to perform the service request check.

func (*Auth) NewAuthClient

func (a *Auth) NewAuthClient() (*http.Client, error)

NewAuthClient creates a new HTTP client with an AuthClient as the transport, allowing access token injection on each request.

func (*Auth) Start

func (a *Auth) Start() chan error

Start initializes the auth service and begins a periodic refresh using a ticker. This function ensures that the service is started only once and returns an error channel that reports any issues encountered during refreshes. Consumers of this function must listen to the returned error channel to prevent it from blocking when errors occur.

type AuthClient

type AuthClient struct {
	// contains filtered or unexported fields
}

func (*AuthClient) RoundTrip

func (ac *AuthClient) RoundTrip(r *http.Request) (*http.Response, error)

RoundTrip intercepts the HTTP request to inject an access token and then forwards it using the configured roundTripper. It handles request body cleanup and ensures a valid access token is acquired within a timeout.

type AuthProvider

type AuthProvider interface {
	// AuthorizeRequest checks if the request meets the specified authorization requirement.
	// It returns true if the request is authorized, otherwise false, and an error if something goes wrong.
	AuthorizeRequest(r *http.Request, permission string) (isAuthorized bool, err error)

	// IsServiceRequest checks whether the given HTTP request originates from a service.
	// It returns true if the request is identified as a service request, otherwise false.
	IsServiceRequest(r *http.Request) (isService bool)

	// RefreshAccessToken refreshes the current access token.
	// It returns the new access token, the time for the next refresh, and an error if the operation fails.
	RefreshAccessToken() (accessToken *AccessToken, nextRefresh time.Time, err error)

	// RefreshKeys retrieves updated authentication keys and the scheduled time for the next key refresh.
	// It returns a slice of keys, the time for the next refresh, and an error if the operation fails.
	RefreshKeys() (keys []*Key, nextRefresh time.Time, err error)
}

type Config

type Config struct {
	AuthProvider AuthProvider // Provider for authentication logic configuration
}

func (*Config) Validate

func (c *Config) Validate() error

validate checks the Config struct for required fields and returns an error if any required fields are missing

type Key

type Key struct {
	Kid string `json:"kid"` // Kid is the unique identifier for the key.
	Iat int64  `json:"iat"` // Iat is the issued-at time in Unix time (seconds since the epoch).
	Exp int64  `json:"exp"` // Exp is the expiration time in Unix time (seconds since the epoch).
	Alg string `json:"alg"` // Alg specifies the algorithm used with the key (e.g., "RS256").
	Pem string `json:"pem"` // Key contains the RSA public key in PEM format.
}

func (*Key) Validate

func (k *Key) Validate() error

validate checks the Key struct for required fields and returns an error if any required fields are missing

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL