chore: import upstream snapshot with attribution
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,396 @@
|
||||
# Auth Example
|
||||
|
||||
This example demonstrates how to use the auth wrappers to protect your microservices with authentication and authorization.
|
||||
|
||||
## Overview
|
||||
|
||||
The example includes:
|
||||
|
||||
- **Server** - A Greeter service with:
|
||||
- Protected endpoint: `Greeter.Hello` (requires auth)
|
||||
- Public endpoint: `Greeter.Health` (no auth required)
|
||||
|
||||
- **Client** - Makes calls to the server:
|
||||
- With authentication (successful)
|
||||
- Without authentication (fails as expected)
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Client │
|
||||
│ ┌────────────────────────────────┐ │
|
||||
│ │ AuthClient Wrapper │ │
|
||||
│ │ - Adds Bearer token │ │
|
||||
│ │ - To all requests │ │
|
||||
│ └────────────────────────────────┘ │
|
||||
└──────────────┬──────────────────────────┘
|
||||
│ RPC with Authorization: Bearer <token>
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Server │
|
||||
│ ┌────────────────────────────────┐ │
|
||||
│ │ AuthHandler Wrapper │ │
|
||||
│ │ - Extracts token │ │
|
||||
│ │ - Verifies with auth.Inspect()│ │
|
||||
│ │ - Checks with rules.Verify() │ │
|
||||
│ │ - Returns 401/403 if denied │ │
|
||||
│ └────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────────────────────────┐ │
|
||||
│ │ Handler (Greeter.Hello) │ │
|
||||
│ │ - Gets account from context │ │
|
||||
│ │ - Processes request │ │
|
||||
│ └────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
```
|
||||
examples/auth/
|
||||
├── README.md # This file
|
||||
├── proto/
|
||||
│ ├── greeter.proto # Service definition
|
||||
│ └── greeter.pb.go # Generated Go code
|
||||
├── server/
|
||||
│ └── main.go # Protected service
|
||||
└── client/
|
||||
└── main.go # Client with auth
|
||||
```
|
||||
|
||||
## Running the Example
|
||||
|
||||
### 1. Start the Server
|
||||
|
||||
```bash
|
||||
cd server
|
||||
go run main.go
|
||||
```
|
||||
|
||||
The server will:
|
||||
- Start the Greeter service
|
||||
- Apply auth wrapper to protect endpoints
|
||||
- Generate a test token and print it
|
||||
|
||||
Output:
|
||||
```
|
||||
=== Test Token Generated ===
|
||||
Use this token to test the client:
|
||||
TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... go run client/main.go
|
||||
|
||||
2026/02/11 10:00:00 Server [greeter] Listening on [::]:54321
|
||||
```
|
||||
|
||||
### 2. Run the Client (With Auth)
|
||||
|
||||
In a new terminal:
|
||||
|
||||
```bash
|
||||
cd client
|
||||
TOKEN=<token-from-server> go run main.go
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
=== Test 1: Protected endpoint WITH auth ===
|
||||
Response: Hello, test-user!
|
||||
|
||||
=== Test 2: Public endpoint (no auth needed) ===
|
||||
Health Status: ok
|
||||
|
||||
=== Test 3: Protected endpoint WITHOUT auth (should fail) ===
|
||||
Expected error: {"id":"greeter","code":401,"detail":"missing authorization token","status":"Unauthorized"}
|
||||
```
|
||||
|
||||
### 3. Run the Client (Without Auth)
|
||||
|
||||
```bash
|
||||
cd client
|
||||
go run main.go
|
||||
```
|
||||
|
||||
This will auto-generate a token for testing.
|
||||
|
||||
## Code Walkthrough
|
||||
|
||||
### Server Setup
|
||||
|
||||
```go
|
||||
// 1. Create auth provider
|
||||
// For this example we use the noop auth (accepts all tokens)
|
||||
// In production, use JWT or a custom auth provider
|
||||
authProvider := noop.NewAuth()
|
||||
|
||||
// 2. Create authorization rules
|
||||
rules := auth.NewRules()
|
||||
rules.Grant(&auth.Rule{
|
||||
ID: "public-health",
|
||||
Scope: "",
|
||||
Resource: &auth.Resource{Endpoint: "Greeter.Health"},
|
||||
Access: auth.AccessGranted,
|
||||
})
|
||||
|
||||
// 3. Wrap service with auth handler
|
||||
service := micro.NewService(
|
||||
micro.Name("greeter"),
|
||||
micro.WrapHandler(
|
||||
authWrapper.AuthHandler(authWrapper.HandlerOptions{
|
||||
Auth: authProvider,
|
||||
Rules: rules,
|
||||
SkipEndpoints: []string{"Greeter.Health"},
|
||||
}),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### Client Setup
|
||||
|
||||
```go
|
||||
// 1. Get or generate token
|
||||
token := os.Getenv("TOKEN")
|
||||
|
||||
// 2. Wrap client with auth
|
||||
service := micro.NewService(
|
||||
micro.Name("greeter.client"),
|
||||
micro.WrapClient(
|
||||
authWrapper.FromToken(token),
|
||||
),
|
||||
)
|
||||
|
||||
// 3. Make calls (token automatically added)
|
||||
greeterClient := pb.NewGreeterService("greeter", service.Client())
|
||||
rsp, err := greeterClient.Hello(ctx, &pb.Request{Name: "John"})
|
||||
```
|
||||
|
||||
### Handler Implementation
|
||||
|
||||
```go
|
||||
func (g *Greeter) Hello(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
|
||||
// Get account from context (added by auth wrapper)
|
||||
acc, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
return errors.Unauthorized("greeter", "authentication required")
|
||||
}
|
||||
|
||||
rsp.Msg = "Hello, " + acc.ID + "!"
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Auth Wrapper Features
|
||||
|
||||
### Server Wrapper (`AuthHandler`)
|
||||
|
||||
- **Token Extraction**: Reads `Authorization: Bearer <token>` from metadata
|
||||
- **Token Verification**: Validates token using `auth.Inspect()`
|
||||
- **Authorization**: Checks permissions using `rules.Verify()`
|
||||
- **Context Injection**: Adds account to context for handlers
|
||||
- **Error Handling**: Returns 401/403 with clear error messages
|
||||
- **Skip Endpoints**: Allows public endpoints without auth
|
||||
|
||||
### Client Wrapper (`AuthClient`)
|
||||
|
||||
- **Automatic Token Injection**: Adds Bearer token to all requests
|
||||
- **Context-Aware**: Can extract account from context
|
||||
- **Static Token**: Use `FromToken()` for pre-generated tokens
|
||||
- **Dynamic Token**: Use `FromContext()` to generate per-request
|
||||
|
||||
## Auth Strategies
|
||||
|
||||
### 1. All Endpoints Protected
|
||||
|
||||
```go
|
||||
micro.WrapHandler(
|
||||
authWrapper.AuthRequired(authProvider, rules),
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Some Public Endpoints
|
||||
|
||||
```go
|
||||
micro.WrapHandler(
|
||||
authWrapper.PublicEndpoints(authProvider, rules, []string{
|
||||
"Health.Check",
|
||||
"Status.Version",
|
||||
}),
|
||||
)
|
||||
```
|
||||
|
||||
### 3. Optional Auth (Extract but Don't Enforce)
|
||||
|
||||
```go
|
||||
micro.WrapHandler(
|
||||
authWrapper.AuthOptional(authProvider),
|
||||
)
|
||||
```
|
||||
|
||||
## Authorization Rules
|
||||
|
||||
### Grant Public Access
|
||||
|
||||
```go
|
||||
rules.Grant(&auth.Rule{
|
||||
ID: "public",
|
||||
Scope: "", // No scope = public
|
||||
Resource: &auth.Resource{Endpoint: "Health.Check"},
|
||||
Access: auth.AccessGranted,
|
||||
})
|
||||
```
|
||||
|
||||
### Require Authentication
|
||||
|
||||
```go
|
||||
rules.Grant(&auth.Rule{
|
||||
ID: "authenticated",
|
||||
Scope: "*", // Any authenticated user
|
||||
Resource: &auth.Resource{Endpoint: "*"},
|
||||
Access: auth.AccessGranted,
|
||||
})
|
||||
```
|
||||
|
||||
### Require Specific Scope
|
||||
|
||||
```go
|
||||
rules.Grant(&auth.Rule{
|
||||
ID: "admin-only",
|
||||
Scope: "admin", // Only admin scope
|
||||
Resource: &auth.Resource{Endpoint: "Admin.*"},
|
||||
Access: auth.AccessGranted,
|
||||
})
|
||||
```
|
||||
|
||||
### Deny Access
|
||||
|
||||
```go
|
||||
rules.Grant(&auth.Rule{
|
||||
ID: "deny-delete",
|
||||
Scope: "*",
|
||||
Resource: &auth.Resource{Endpoint: "User.Delete"},
|
||||
Access: auth.AccessDenied,
|
||||
Priority: 100, // Higher priority = evaluated first
|
||||
})
|
||||
```
|
||||
|
||||
## Testing Without Server
|
||||
|
||||
You can test auth logic without a running server:
|
||||
|
||||
```go
|
||||
import "go-micro.dev/v5/auth/noop"
|
||||
|
||||
// Create auth provider (noop for testing)
|
||||
authProvider := noop.NewAuth()
|
||||
|
||||
// Generate account
|
||||
acc, _ := authProvider.Generate("test-user", auth.WithScopes("admin"))
|
||||
|
||||
// Generate token
|
||||
token, _ := authProvider.Token(auth.WithCredentials(acc.ID, acc.Secret))
|
||||
|
||||
// Verify token
|
||||
verified, _ := authProvider.Inspect(token.AccessToken)
|
||||
fmt.Println(verified.ID) // Returns a generated UUID
|
||||
```
|
||||
|
||||
## Production Considerations
|
||||
|
||||
### 1. Use Production Auth Provider
|
||||
|
||||
The noop auth provider (`auth.NewAuth()`) is for development only. It accepts any token.
|
||||
|
||||
For production, implement a proper auth provider or use the JWT implementation:
|
||||
|
||||
```go
|
||||
// Option 1: Implement custom auth.Auth interface
|
||||
type MyAuth struct {
|
||||
// Your implementation
|
||||
}
|
||||
|
||||
func (m *MyAuth) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
|
||||
// Generate real accounts
|
||||
}
|
||||
|
||||
func (m *MyAuth) Inspect(token string) (*auth.Account, error) {
|
||||
// Verify real tokens (JWT, OAuth, etc.)
|
||||
}
|
||||
|
||||
// Option 2: Use JWT auth (requires jwt package implementation)
|
||||
// Note: The jwt package in auth/jwt depends on an external plugin
|
||||
// You may need to implement your own JWT auth or use a third-party library
|
||||
```
|
||||
|
||||
### 3. Add Gateway Auth
|
||||
|
||||
If using HTTP gateway:
|
||||
|
||||
```go
|
||||
// Add auth to HTTP gateway
|
||||
http.Handle("/", gateway.Handler(
|
||||
gateway.WithAuth(authProvider),
|
||||
))
|
||||
```
|
||||
|
||||
### 4. Service-to-Service Auth
|
||||
|
||||
Services calling other services:
|
||||
|
||||
```go
|
||||
// Service A calls Service B with its own token
|
||||
client := micro.NewService(
|
||||
micro.WrapClient(
|
||||
authWrapper.FromContext(authProvider),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### 5. Token Refresh
|
||||
|
||||
```go
|
||||
// Check if token is expiring
|
||||
if time.Until(token.Expiry) < 5*time.Minute {
|
||||
token, _ = authProvider.Token(auth.WithToken(token.RefreshToken))
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Error: "missing authorization token"
|
||||
|
||||
- **Cause**: Client didn't send Authorization header
|
||||
- **Fix**: Wrap client with `authWrapper.FromToken(token)`
|
||||
|
||||
### Error: "invalid token"
|
||||
|
||||
- **Cause**: Token is expired or malformed
|
||||
- **Fix**: Generate a new token
|
||||
|
||||
### Error: "access denied"
|
||||
|
||||
- **Cause**: Account doesn't have required permissions
|
||||
- **Fix**: Check authorization rules with `rules.List()`
|
||||
|
||||
### Error: "token verification failed"
|
||||
|
||||
- **Cause**: Server can't verify token (wrong keys, expired, etc.)
|
||||
- **Fix**: Ensure server and client use same auth provider
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Read the [Auth Documentation](/docs/auth)
|
||||
- Explore [JWT Auth](/auth/jwt)
|
||||
- Try [Custom Auth Provider](/examples/auth/custom)
|
||||
- See [Multi-Tenant Auth](/examples/auth/multi-tenant)
|
||||
|
||||
## Summary
|
||||
|
||||
The auth wrappers make it easy to:
|
||||
|
||||
1. **Protect services**: Add `WrapHandler(AuthHandler(...))`
|
||||
2. **Add authentication to clients**: Add `WrapClient(FromToken(...))`
|
||||
3. **Control access**: Define rules with `rules.Grant()`
|
||||
4. **Access account info**: Use `auth.AccountFromContext(ctx)`
|
||||
|
||||
That's it! Your microservices now have enterprise-grade authentication and authorization.
|
||||
Reference in New Issue
Block a user