Skip to content

Conversation

@appleboy
Copy link
Owner

  • Add service-to-service authentication options for external HTTP APIs, supporting none, simple secret, or HMAC modes
  • Introduce configuration for authentication mode, secret, and header names for both Auth API and Token API
  • Implement a reusable AuthConfig with logic for adding authentication headers to requests and verifying server-side HMAC signatures
  • Update external authentication and token provider flows to attach authentication headers to API requests based on configuration
  • Add comprehensive unit tests for all service-to-service authentication header modes and HMAC verification
  • Document authentication modes and secure API configuration in detail in both CLAUDE.md and README.md

- Add service-to-service authentication options for external HTTP APIs, supporting none, simple secret, or HMAC modes
- Introduce configuration for authentication mode, secret, and header names for both Auth API and Token API
- Implement a reusable AuthConfig with logic for adding authentication headers to requests and verifying server-side HMAC signatures
- Update external authentication and token provider flows to attach authentication headers to API requests based on configuration
- Add comprehensive unit tests for all service-to-service authentication header modes and HMAC verification
- Document authentication modes and secure API configuration in detail in both CLAUDE.md and README.md

Signed-off-by: appleboy <appleboy.tw@gmail.com>
Copilot AI review requested due to automatic review settings January 17, 2026 04:02
@netlify
Copy link

netlify bot commented Jan 17, 2026

Deploy Preview for authgate-demo ready!

Name Link
🔨 Latest commit 37a44fd
🔍 Latest deploy log https://app.netlify.com/projects/authgate-demo/deploys/696b0ce5f872bb0008b1b4d2
😎 Deploy Preview https://deploy-preview-15--authgate-demo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds configurable service-to-service authentication for external HTTP APIs, enabling AuthGate to securely communicate with external authentication and token services. The feature supports three authentication modes (none, simple secret, and HMAC-SHA256) with comprehensive configuration options.

Changes:

  • Introduced a new httpclient.AuthConfig struct with methods for adding authentication headers (simple and HMAC modes) and server-side HMAC signature verification
  • Updated external authentication provider and token provider to integrate authentication headers into API requests
  • Added environment variables for configuring authentication modes, secrets, and custom headers for both Auth API and Token API
  • Documented authentication modes, configuration examples, and server-side verification in README.md and CLAUDE.md

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
internal/httpclient/auth.go New authentication configuration module with header generation and HMAC verification logic
internal/httpclient/auth_test.go Comprehensive test suite covering all authentication modes and edge cases
internal/token/http_api.go Integration of AuthConfig to add authentication headers to all token API requests
internal/auth/http_api.go Integration of AuthConfig to add authentication headers to authentication API requests
internal/config/config.go Added configuration fields for HTTP API and Token API authentication settings
README.md Documentation of authentication modes, configuration examples, and usage instructions
CLAUDE.md Detailed technical documentation of service-to-service authentication implementation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +301 to +329
func TestAuthConfig_VerifyHMACSignature_ExpiredTimestamp(t *testing.T) {
config := &AuthConfig{
Secret: "test-secret",
}

body := []byte(`{"username":"test"}`)
// Timestamp from 10 minutes ago
timestamp := time.Now().Add(-10 * time.Minute).Unix()
signature := config.calculateHMACSignature(timestamp, "POST", "/api/auth", body)

req, err := http.NewRequestWithContext(
context.Background(),
"POST",
testExampleAuthURL,
bytes.NewBuffer(body),
)
if err != nil {
t.Fatalf("Failed to create request: %v", err)
}

req.Header.Set(testXSignature, signature)
req.Header.Set(testXTimestamp, strconv.FormatInt(timestamp, 10))

// Verify with 5 minute max age - should fail
err = config.VerifyHMACSignature(req, 5*time.Minute)
if err == nil {
t.Errorf("VerifyHMACSignature() error = nil, want expired error")
}
}
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for future timestamps in HMAC verification. The VerifyHMACSignature function should reject timestamps that are too far in the future (clock skew attacks), but there's no test case to verify this behavior. Add a test case that sends a timestamp 10 minutes in the future and verifies it's rejected.

Copilot uses AI. Check for mistakes.
}

requestTime := time.Unix(timestamp, 0)
if time.Since(requestTime) > maxAge {
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The timestamp validation only checks if the timestamp is too old (in the past) but does not validate if the timestamp is in the future. An attacker could send a request with a timestamp far in the future, which would bypass the replay protection. Add a check to ensure the timestamp is not more than a reasonable amount (e.g., 1 minute) in the future.

Suggested change
if time.Since(requestTime) > maxAge {
// Reject timestamps that are too old or too far in the future.
// We treat maxAge as a symmetric window around the current time.
delta := time.Since(requestTime)
if delta > maxAge || delta < -maxAge {

Copilot uses AI. Check for mistakes.
Comment on lines +810 to +820
import "github.com/appleboy/authgate/internal/httpclient"

// Initialize auth config (server side)
authConfig := httpclient.NewAuthConfig("hmac", "your-hmac-secret")

// Verify incoming request
err := authConfig.VerifyHMACSignature(req, 5*time.Minute)
if err != nil {
http.Error(w, "Authentication failed", http.StatusUnauthorized)
return
}
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server-side verification example is incomplete. After calling VerifyHMACSignature, the request body is consumed and needs to be restored before the handler can read it. The example should include a note or code showing that after verification, the body must be manually restored or that VerifyHMACSignature should be updated to restore the body automatically.

Suggested change
import "github.com/appleboy/authgate/internal/httpclient"
// Initialize auth config (server side)
authConfig := httpclient.NewAuthConfig("hmac", "your-hmac-secret")
// Verify incoming request
err := authConfig.VerifyHMACSignature(req, 5*time.Minute)
if err != nil {
http.Error(w, "Authentication failed", http.StatusUnauthorized)
return
}
import (
"bytes"
"io"
"net/http"
"time"
"github.com/appleboy/authgate/internal/httpclient"
)
// Initialize auth config (server side)
authConfig := httpclient.NewAuthConfig("hmac", "your-hmac-secret")
// Read and buffer the request body so it can be reused
bodyBytes, err := io.ReadAll(req.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusBadRequest)
return
}
// Close the original body reader
_ = req.Body.Close()
// Set body for signature verification
req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
// Verify incoming request
err = authConfig.VerifyHMACSignature(req, 5*time.Minute)
if err != nil {
http.Error(w, "Authentication failed", http.StatusUnauthorized)
return
}
// Restore body for further handler processing
req.Body = io.NopCloser(bytes.NewReader(bodyBytes))

Copilot uses AI. Check for mistakes.
Comment on lines +552 to +563
import "github.com/appleboy/authgate/internal/httpclient"

authConfig := &httpclient.AuthConfig{
Mode: httpclient.AuthModeHMAC,
Secret: "your-shared-secret-key",
}

// Verify signature (checks timestamp age and signature validity)
err := authConfig.VerifyHMACSignature(req, 5*time.Minute)
if err != nil {
return http.StatusUnauthorized
}
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server-side validation example is incomplete. After calling VerifyHMACSignature, the request body is consumed and needs to be restored before the handler can read it. The example should include a note or code showing that after verification, the body must be manually restored or that VerifyHMACSignature should be updated to restore the body automatically.

Suggested change
import "github.com/appleboy/authgate/internal/httpclient"
authConfig := &httpclient.AuthConfig{
Mode: httpclient.AuthModeHMAC,
Secret: "your-shared-secret-key",
}
// Verify signature (checks timestamp age and signature validity)
err := authConfig.VerifyHMACSignature(req, 5*time.Minute)
if err != nil {
return http.StatusUnauthorized
}
import (
"bytes"
"io"
"net/http"
"time"
"github.com/appleboy/authgate/internal/httpclient"
)
authConfig := &httpclient.AuthConfig{
Mode: httpclient.AuthModeHMAC,
Secret: "your-shared-secret-key",
}
// Read and buffer the body so it can be used both for verification and by handlers.
bodyBytes, err := io.ReadAll(req.Body)
if err != nil {
return http.StatusBadRequest
}
// Restore body for signature verification.
req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
// Verify signature (checks timestamp age and signature validity).
err = authConfig.VerifyHMACSignature(req, 5*time.Minute)
if err != nil {
return http.StatusUnauthorized
}
// Restore body again so downstream handlers can read it as usual.
req.Body = io.NopCloser(bytes.NewReader(bodyBytes))

Copilot uses AI. Check for mistakes.
Comment on lines +123 to +176
func TestAuthConfig_AddAuthHeaders_HMAC(t *testing.T) {
config := &AuthConfig{
Mode: AuthModeHMAC,
Secret: "test-secret-hmac",
}

body := []byte(`{"username":"test","password":"pass123"}`)
req, err := http.NewRequestWithContext(
context.Background(),
"POST",
testExampleAuthURL,
bytes.NewBuffer(body),
)
if err != nil {
t.Fatalf("Failed to create request: %v", err)
}

err = config.AddAuthHeaders(req, body)
if err != nil {
t.Fatalf("AddAuthHeaders() error = %v", err)
}

// Check that all required headers are present
signature := req.Header.Get(testXSignature)
if signature == "" {
t.Errorf("Expected X-Signature header to be set")
}

timestamp := req.Header.Get(testXTimestamp)
if timestamp == "" {
t.Errorf("Expected X-Timestamp header to be set")
}

nonce := req.Header.Get(testXNonce)
if nonce == "" {
t.Errorf("Expected X-Nonce header to be set")
}

// Verify signature format (should be hex string)
if len(signature) != 64 { // SHA256 hex string is 64 characters
t.Errorf("Signature length = %d, want 64", len(signature))
}

// Verify timestamp is recent (within 1 second)
ts, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil {
t.Errorf("Failed to parse timestamp: %v", err)
}

timeDiff := time.Now().Unix() - ts
if timeDiff > 1 {
t.Errorf("Timestamp is too old: %d seconds", timeDiff)
}
}
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for HMAC mode without a secret. While the addHMACAuth method does check for an empty secret and returns an error, there should be a test case similar to the "Simple mode without secret" test to verify this error handling works correctly for HMAC mode as well.

Copilot uses AI. Check for mistakes.
- Update HMAC signature calculation to include query parameters
- Restore request body after signature verification to allow further processing
- Add utility to obtain full request path with query parameters
- Introduce tests to verify body preservation after HMAC signature verification
- Add tests ensuring HMAC signature covers query parameters for improved security

Signed-off-by: appleboy <appleboy.tw@gmail.com>
@appleboy appleboy merged commit 36057d3 into main Jan 17, 2026
15 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants