feat: JWT and API key auth interceptor examples#32
Conversation
Config-controlled auth using go-grpc-middleware/v2 auth + golang-jwt/jwt/v5. AuthConfig embedded in config.Config (same pattern as cbConfig.Config), Setup() called from main(). No env vars set = no-op. - JWTAuthFunc: HMAC-SHA256 Bearer token validation with claims context - APIKeyAuthFunc: x-api-key header validation against configured keys - Tests for valid/invalid/expired/missing tokens and keys
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a config-driven authentication package ( Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant AuthI as Auth Interceptor
participant Handler as gRPC Handler
participant Ctx as Request Context
Client->>AuthI: gRPC request + metadata (Authorization / x-api-key)
rect rgba(100,150,200,0.5)
Note over AuthI: JWT path
AuthI->>AuthI: Extract Bearer token
AuthI->>AuthI: Validate HS256 signature & claims
AuthI->>Ctx: Store Claims in ctx
end
rect rgba(200,150,100,0.5)
Note over AuthI: API-key path
AuthI->>AuthI: Extract x-api-key header
AuthI->>AuthI: Validate key against allowed set
end
AuthI->>Handler: Authorized request (ctx)
Handler->>Ctx: ClaimsFromContext()
Handler->>Client: gRPC response
sequenceDiagram
participant App
participant Config
participant Auth as auth.Setup
participant Framework
App->>Config: Load AuthConfig (JWT_SECRET, API_KEYS)
Config-->>App: AuthConfig
App->>Auth: Setup(ctx, AuthConfig)
rect rgba(150,200,100,0.5)
Note over Auth: Conditional interceptor registration
Auth->>Auth: If JWTSecret set -> add JWT interceptor
Auth->>Auth: If APIKeys set -> add API-key interceptor
end
Auth->>Framework: Register server interceptors
App->>Framework: Initialize service/framework
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds example gRPC authentication middleware to the service template, enabling opt-in JWT (HMAC) or API-key validation via environment-driven config and registering the interceptors during service startup.
Changes:
- Introduces
service/authpackage with JWT + API-key auth functions and unit tests. - Embeds
auth.AuthConfigintoconfig.Configand wiresauth.Setup(...)intomain.go. - Updates template docs/examples (
local.env.example,AGENTS.md) to reference auth configuration.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
{{cookiecutter.app_name}}/service/auth/auth.go |
Adds config-driven interceptor registration plus JWT and API-key AuthFuncs. |
{{cookiecutter.app_name}}/service/auth/auth_test.go |
Unit tests for JWT and API-key auth behaviors. |
{{cookiecutter.app_name}}/main.go |
Calls auth.Setup(...) during startup to register interceptors when configured. |
{{cookiecutter.app_name}}/config/config.go |
Embeds auth.AuthConfig so envconfig can populate auth settings. |
{{cookiecutter.app_name}}/local.env.example |
Documents JWT/API key env vars. |
{{cookiecutter.app_name}}/AGENTS.md |
Updates repo tree to include the new service/auth package. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @{{cookiecutter.app_name}}/AGENTS.md:
- Line 38: The AGENTS.md line claiming "uncomment in main.go to enable" for
auth.go is stale; update the wording to reflect that authentication is already
wired up by calling auth.Setup(...) in main.go rather than requiring
uncommenting. Edit the note referencing auth.go to say that example interceptors
are provided and that auth.Setup(...) is invoked in main.go (so users only need
to configure/enable via configuration or the auth package, not uncomment code).
Ensure you reference auth.go and the auth.Setup(...) call in main.go so the doc
clearly points to where auth is initialized.
In @{{cookiecutter.app_name}}/service/auth/auth.go:
- Around line 103-119: APIKeyAuthFunc currently inserts keys as-is into keySet
and validates the raw header value, which allows empty/whitespace keys to bypass
auth; update the constructor and validator to normalize and reject empties: when
building keySet in APIKeyAuthFunc, call strings.TrimSpace on each entry and skip
adding if the trimmed string is empty; in the returned auth func, read the
header value keys[0], trim it with strings.TrimSpace, immediately reject if the
trimmed value is empty, then check membership against keySet using the trimmed
value (use the same normalization as during construction). Ensure you import
strings if not present.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 88d5b496-1125-486d-a2b3-69773f2ef3be
📒 Files selected for processing (6)
{{cookiecutter.app_name}}/AGENTS.md{{cookiecutter.app_name}}/config/config.go{{cookiecutter.app_name}}/local.env.example{{cookiecutter.app_name}}/main.go{{cookiecutter.app_name}}/service/auth/auth.go{{cookiecutter.app_name}}/service/auth/auth_test.go
- Add golang-jwt/jwt/v5 and promote interceptors to direct deps in go.mod - Support both JWT + API key simultaneously via eitherAuthFunc (accept either) - Sanitize empty/whitespace API keys to prevent accidental bypass - Fix AGENTS.md stale "uncomment" wording → env-var-controlled - Fix local.env.example wording to match config-driven behavior
Skip auth for methods matching ColdBrew's default FilterMethods (healthcheck, readycheck, serverreflectioninfo) plus grpc.health.v1.Health so Kubernetes probes work without credentials.
Replace substring matching with exact full method path matching to prevent methods containing "healthcheck" as a substring from bypassing auth. Uses cookiecutter template variables for the service-specific paths.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @{{cookiecutter.app_name}}/service/auth/auth.go:
- Around line 138-140: Return a generic unauthenticated error to clients instead
of exposing JWT parser details: replace the status.Errorf(codes.Unauthenticated,
"invalid token: %v", err) return with a generic message like
status.Errorf(codes.Unauthenticated, "unauthenticated") and log the original
parser/validation error internally (using your package logger) when err != nil
or token.Valid is false; update the error handling in the function where
token.Valid and err are checked to avoid leaking parser details while preserving
internal diagnostics.
- Around line 53-71: Change the Setup function to accept a context.Context as
its first parameter (i.e., func Setup(ctx context.Context, cfg AuthConfig))
instead of calling context.Background(); keep the existing auth selection logic
(eitherAuthFunc, JWTAuthFunc, APIKeyAuthFunc, skipMethodsAuthFunc) but pass the
provided ctx into the interceptor registrations so that
interceptors.AddUnaryServerInterceptor(ctx,
grpcauth.UnaryServerInterceptor(authFunc)) and
interceptors.AddStreamServerInterceptor(ctx,
grpcauth.StreamServerInterceptor(authFunc)) are used rather than
context.Background().
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 33ec2b5e-9503-4acb-acda-909245158c09
📒 Files selected for processing (5)
{{cookiecutter.app_name}}/AGENTS.md{{cookiecutter.app_name}}/go.mod{{cookiecutter.app_name}}/local.env.example{{cookiecutter.app_name}}/service/auth/auth.go{{cookiecutter.app_name}}/service/auth/auth_test.go
✅ Files skipped from review due to trivial changes (2)
- {{cookiecutter.app_name}}/local.env.example
- {{cookiecutter.app_name}}/AGENTS.md
🚧 Files skipped from review as they are similar to previous changes (1)
- {{cookiecutter.app_name}}/service/auth/auth_test.go
Add all transitive deps required by auth package (golang-jwt/jwt, go-grpc-middleware auth, interceptors, etc.) so go.mod is self-consistent and the template builds without go mod tidy.
- Setup() now takes context.Context as first param (ColdBrew convention) - JWT validation errors return generic "invalid token" instead of exposing parser details to clients
Swagger UI now shows Authorize button with BearerJWT and APIKey options. Health/ready endpoints marked as no-auth-required.
- GenerateTestToken() for local dev/testing JWT generation - README: authentication section with enable commands and test token example - AGENTS.md: authentication added to Key Patterns
- Log auth failures at warn level with method name and error details (auth runs before ResponseTimeLoggingInterceptor so failures were invisible) - Add HTTP_HEADER_PREFIXES=x-api-key to local.env.example for API key forwarding via grpc-gateway
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @{{cookiecutter.app_name}}/service/auth/auth.go:
- Around line 55-64: The Setup function currently checks len(cfg.APIKeys) before
APIKeyAuthFunc normalizes keys, causing whitespace-only entries to enable auth;
normalize cfg.APIKeys first (trim each entry and filter out empty strings) into
a new slice and use that normalized slice for the branch decision and when
calling APIKeyAuthFunc (including when creating eitherAuthFunc with
JWTAuthFunc/APIKeyAuthFunc); apply the same normalization logic to the other
Setup/registration site mentioned (the second occurrence using APIKeyAuthFunc)
so an empty normalized set is treated as "no API keys" and auth is not
registered.
- Around line 89-100: eitherAuthFunc currently lets underlying auth funcs emit
errors and returns the lastErr directly; change it to suppress per-leaf logging
and only emit a single warning and a generic unauthenticated gRPC error after
all strategies fail: in eitherAuthFunc keep calling each grpcauth.AuthFunc and
record the last error silently, but if no fn succeeds then call your logger once
(e.g., logger.Warnf("authentication failed: %v", lastErr)) and return
status.Error(codes.Unauthenticated, "authentication failed") instead of
returning lastErr; apply the same pattern to the other combined auth-wrapper
functions referenced in the review (the analogous wrappers around lines 132-145
and 163-179) so leaf auth funcs don’t leak internal error details and the client
always sees a consistent unauthenticated response.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6eb45b00-a6b5-44b7-b41e-c529c3e04e05
📒 Files selected for processing (7)
{{cookiecutter.app_name}}/AGENTS.md{{cookiecutter.app_name}}/README.md{{cookiecutter.app_name}}/go.mod{{cookiecutter.app_name}}/local.env.example{{cookiecutter.app_name}}/main.go{{cookiecutter.app_name}}/proto/{{cookiecutter.app_name|lower}}.proto{{cookiecutter.app_name}}/service/auth/auth.go
✅ Files skipped from review due to trivial changes (3)
- {{cookiecutter.app_name}}/local.env.example
- {{cookiecutter.app_name}}/README.md
- {{cookiecutter.app_name}}/go.mod
🚧 Files skipped from review as they are similar to previous changes (1)
- {{cookiecutter.app_name}}/main.go
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Normalize API keys in Setup() before len() check (whitespace-only bypass) - Move auth logging to withAuthLogging/eitherAuthFunc (no noisy logs on fallthrough) - Rename shadowed skip variable to found - Add eitherAuthFunc tests (both-configured path)
There was a problem hiding this comment.
🧹 Nitpick comments (1)
{{cookiecutter.app_name}}/service/auth/auth_test.go (1)
114-145: Add a regression test for whitespace-only API keys.The PR explicitly hardens key sanitization; adding a test where configured keys include whitespace-only entries would lock this behavior and prevent bypass regressions.
✅ Suggested test addition
+func TestAPIKeyAuthFunc_WhitespaceOnlyConfiguredKeysIgnored(t *testing.T) { + authFunc := APIKeyAuthFunc([]string{" ", "\t", "valid-key"}) + + _, err := authFunc(ctxWithAPIKey(" ")) + require.Error(t, err) + assert.Equal(t, codes.Unauthenticated, status.Code(err)) + + _, err = authFunc(ctxWithAPIKey("valid-key")) + require.NoError(t, err) +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @{{cookiecutter.app_name}}/service/auth/auth_test.go around lines 114 - 145, Add a regression test to ensure whitespace-only configured API keys are ignored by APIKeyAuthFunc: create a test (e.g., TestAPIKeyAuthFunc_WhitespaceKey) that constructs authFunc := APIKeyAuthFunc([]string{"key-1", " "}) and verifies a valid call with ctxWithAPIKey("key-1") succeeds while attempts using a whitespace key (ctxWithAPIKey(" ")) or a missing/invalid key still return UnaAuthenticated errors; reference APIKeyAuthFunc and ctxWithAPIKey to locate where to add the test alongside the existing TestAPIKeyAuthFunc_* tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In @{{cookiecutter.app_name}}/service/auth/auth_test.go:
- Around line 114-145: Add a regression test to ensure whitespace-only
configured API keys are ignored by APIKeyAuthFunc: create a test (e.g.,
TestAPIKeyAuthFunc_WhitespaceKey) that constructs authFunc :=
APIKeyAuthFunc([]string{"key-1", " "}) and verifies a valid call with
ctxWithAPIKey("key-1") succeeds while attempts using a whitespace key
(ctxWithAPIKey(" ")) or a missing/invalid key still return UnaAuthenticated
errors; reference APIKeyAuthFunc and ctxWithAPIKey to locate where to add the
test alongside the existing TestAPIKeyAuthFunc_* tests.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 46ed88d2-44e8-4231-bf89-b25d658c8699
📒 Files selected for processing (2)
{{cookiecutter.app_name}}/service/auth/auth.go{{cookiecutter.app_name}}/service/auth/auth_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
- {{cookiecutter.app_name}}/service/auth/auth.go
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- TrimSpace cfg.JWTSecret to prevent whitespace-only secrets enabling auth - Use "err" log key consistently (matches template convention) - README: clarify auth applies to gRPC RPCs only, not HTTP admin endpoints
There was a problem hiding this comment.
🧹 Nitpick comments (1)
{{cookiecutter.app_name}}/README.md (1)
109-113: Clarify the package forauth.GenerateTestToken.The code example is helpful, but consider adding a note about where
authcomes from to help users quickly locate the function.📝 Suggested improvement
Generate a test JWT token: ```go +// In your test code, import "{{cookiecutter.app_name}}/service/auth" token, _ := auth.GenerateTestToken("a-string-secret-at-least-256-bits-long", "test-user", 1*time.Hour)Or alternatively, mention the package in prose: "Generate a test JWT token using `service/auth.GenerateTestToken`:" </details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against the current code and only fix it if needed.
In @{{cookiecutter.app_name}}/README.md around lines 109 - 113, Clarify where
auth.GenerateTestToken comes from by updating the README example to either show
importing the package or mention it in prose; specifically reference the
service/auth package and the GenerateTestToken function (e.g., "import
"{{cookiecutter.app_name}}/service/auth"" or "use
service/auth.GenerateTestToken") so readers can locate the auth package and call
GenerateTestToken correctly in tests.</details> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In @{{cookiecutter.app_name}}/README.md:
- Around line 109-113: Clarify where auth.GenerateTestToken comes from by
updating the README example to either show importing the package or mention it
in prose; specifically reference the service/auth package and the
GenerateTestToken function (e.g., "import
"{{cookiecutter.app_name}}/service/auth"" or "use
service/auth.GenerateTestToken") so readers can locate the auth package and call
GenerateTestToken correctly in tests.</details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Pro **Run ID**: `d3ec7feb-9edc-4a32-8f3a-710bedce3028` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 3749c762cb1be6965c0c7e4991883ebe858d7589 and 9ee942d5bebcb521da772892820efd1814008b8f. </details> <details> <summary>📒 Files selected for processing (2)</summary> * `{{cookiecutter.app_name}}/README.md` * `{{cookiecutter.app_name}}/service/auth/auth.go` </details> <details> <summary>✅ Files skipped from review due to trivial changes (1)</summary> * {{cookiecutter.app_name}}/service/auth/auth.go </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
go-grpc-middleware/v2/interceptors/auth+golang-jwt/jwt/v5AuthConfigembedded inconfig.Config(same pattern ascbConfig.Config),Setup()called frommain()— no env vars set = auth disabled (no-op)JWTAuthFunc: HMAC-SHA256 Bearer token validation with claims context, defense-in-depth signing method checkAPIKeyAuthFunc:x-api-keyheader validation against configured keysTest plan
make buildwithout auth env vars — compiles, auth disabledJWT_SECRET=test, verify auth interceptors are registeredgo test ./service/auth/...— all 10 tests passSummary by CodeRabbit
New Features
Configuration
Documentation
Tests