1
0
mirror of https://github.com/go-micro/go-micro.git synced 2026-04-30 19:15:24 +02:00
Files
2026-02-11 11:25:36 +00:00
..
2026-02-11 11:25:36 +00:00
2026-02-11 11:25:36 +00:00
2026-02-11 11:25:36 +00:00
2026-02-11 11:25:36 +00:00

Auth Wrapper

The auth wrapper package provides server and client wrappers for adding authentication and authorization to your go-micro services.

Installation

import "go-micro.dev/v5/wrapper/auth"

Overview

The auth wrapper consists of three main components:

  1. Server Wrapper (AuthHandler) - Protects service endpoints
  2. Client Wrapper (AuthClient) - Adds auth tokens to requests
  3. Metadata Helpers - Extract/inject tokens from/to metadata

Server Wrapper

The server wrapper enforces authentication and authorization on incoming requests.

Basic Usage

import (
    "go-micro.dev/v5"
    "go-micro.dev/v5/auth/jwt"
    authWrapper "go-micro.dev/v5/wrapper/auth"
)

func main() {
    // Create auth provider
    authProvider, _ := jwt.NewAuth()

    // Create authorization rules
    rules := auth.NewRules()

    // Wrap service with auth
    service := micro.NewService(
        micro.Name("myservice"),
        micro.WrapHandler(
            authWrapper.AuthHandler(authWrapper.HandlerOptions{
                Auth:  authProvider,
                Rules: rules,
            }),
        ),
    )

    service.Run()
}

Configuration Options

type HandlerOptions struct {
    // Auth provider for token verification (required)
    Auth auth.Auth

    // Rules for authorization checks (optional)
    Rules auth.Rules

    // SkipEndpoints is a list of endpoints that don't require auth
    // Format: "Service.Method" e.g., "Greeter.Hello"
    SkipEndpoints []string
}

Auth Flow

For each incoming request:

  1. Check Skip List: If endpoint in SkipEndpoints, skip auth
  2. Extract Token: Get Authorization: Bearer <token> from metadata
  3. Verify Token: Call auth.Inspect(token) to get account
  4. Check Authorization: Call rules.Verify(account, resource)
  5. Inject Context: Add account to context with auth.ContextWithAccount()
  6. Call Handler: Proceed to actual handler

Errors:

  • 401 Unauthorized - Missing or invalid token
  • 403 Forbidden - Token valid but insufficient permissions

Helper Functions

AuthRequired

Enforce auth on all endpoints (no public endpoints):

micro.WrapHandler(
    authWrapper.AuthRequired(authProvider, rules),
)

PublicEndpoints

Allow specific endpoints to be public:

micro.WrapHandler(
    authWrapper.PublicEndpoints(authProvider, rules, []string{
        "Health.Check",
        "Status.Version",
    }),
)

AuthOptional

Extract auth if present but don't enforce (useful for endpoints that behave differently for authenticated users):

micro.WrapHandler(
    authWrapper.AuthOptional(authProvider),
)

With AuthOptional, the handler can check:

func (s *Service) Hello(ctx context.Context, req *Request, rsp *Response) error {
    if acc, ok := auth.AccountFromContext(ctx); ok {
        rsp.Msg = "Hello, " + acc.ID
    } else {
        rsp.Msg = "Hello, anonymous"
    }
    return nil
}

Client Wrapper

The client wrapper adds authentication tokens to outgoing requests.

Basic Usage

import (
    "go-micro.dev/v5"
    "go-micro.dev/v5/client"
    authWrapper "go-micro.dev/v5/wrapper/auth"
)

func main() {
    token := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

    service := micro.NewService(
        micro.Name("myclient"),
        micro.WrapClient(
            authWrapper.FromToken(token),
        ),
    )

    service.Init()

    // All calls now include the token
    client := pb.NewMyServiceClient("myservice", service.Client())
    rsp, err := client.SomeMethod(ctx, &pb.Request{})
}

Configuration Options

type ClientOptions struct {
    // Auth provider for token generation (optional)
    Auth auth.Auth

    // Static token to use (optional)
    // If not provided, will try to extract from context
    Token string
}

Helper Functions

FromToken

Use a static token for all requests:

client.Wrap(
    authWrapper.FromToken("eyJhbGciOi..."),
)

Best for:

  • Pre-generated tokens
  • Service accounts
  • Long-lived tokens

FromContext

Extract account from context and generate token per-request:

client.Wrap(
    authWrapper.FromContext(authProvider),
)

Best for:

  • Service-to-service auth
  • Dynamic token generation
  • Request context propagation

Example:

func (s *Service) HandleRequest(ctx context.Context, req *Request, rsp *Response) error {
    // Account already in context from incoming request

    // Client wrapper extracts account and generates token
    client := pb.NewOtherService("other", s.Client())

    // Token automatically added
    otherRsp, err := client.SomeMethod(ctx, &pb.OtherRequest{})

    return nil
}

Metadata Helpers

Low-level helpers for working with auth tokens in metadata.

TokenFromMetadata

Extract Bearer token from request metadata:

import (
    "go-micro.dev/v5/metadata"
    authWrapper "go-micro.dev/v5/wrapper/auth"
)

func handler(ctx context.Context, req *Request, rsp *Response) error {
    md, _ := metadata.FromContext(ctx)

    token, err := authWrapper.TokenFromMetadata(md)
    if err != nil {
        return err // ErrMissingToken or ErrInvalidToken
    }

    // Use token...
}

Returns:

  • Token string (without "Bearer " prefix)
  • ErrMissingToken - No Authorization header found
  • ErrInvalidToken - Not in "Bearer " format

TokenToMetadata

Add Bearer token to outgoing request metadata:

md := metadata.Metadata{}
md = authWrapper.TokenToMetadata(md, "eyJhbGciOi...")

ctx := metadata.NewContext(context.Background(), md)

// Make RPC call with metadata
client.Call(ctx, req, rsp)

AccountFromMetadata

Extract token and verify in one step:

func handler(ctx context.Context, req *Request, rsp *Response) error {
    md, _ := metadata.FromContext(ctx)

    account, err := authWrapper.AccountFromMetadata(md, authProvider)
    if err != nil {
        return errors.Unauthorized("myservice", "invalid auth")
    }

    // Use account...
    log.Printf("Request from: %s", account.ID)
}

This combines:

  1. TokenFromMetadata(md)
  2. authProvider.Inspect(token)

Complete Example

Server

package main

import (
    "context"
    "go-micro.dev/v5"
    "go-micro.dev/v5/auth"
    "go-micro.dev/v5/auth/jwt"
    authWrapper "go-micro.dev/v5/wrapper/auth"
)

type Greeter struct{}

func (g *Greeter) Hello(ctx context.Context, req *Request, rsp *Response) error {
    // Get authenticated account
    acc, ok := auth.AccountFromContext(ctx)
    if !ok {
        return errors.Unauthorized("greeter", "auth required")
    }

    rsp.Msg = "Hello, " + acc.ID
    return nil
}

func main() {
    authProvider, _ := jwt.NewAuth()
    rules := auth.NewRules()

    service := micro.NewService(
        micro.Name("greeter"),
        micro.WrapHandler(
            authWrapper.PublicEndpoints(authProvider, rules, []string{
                "Greeter.Health",
            }),
        ),
    )

    pb.RegisterGreeterHandler(service.Server(), &Greeter{})
    service.Run()
}

Client

package main

import (
    "context"
    "go-micro.dev/v5"
    authWrapper "go-micro.dev/v5/wrapper/auth"
)

func main() {
    token := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

    service := micro.NewService(
        micro.WrapClient(
            authWrapper.FromToken(token),
        ),
    )

    client := pb.NewGreeterService("greeter", service.Client())
    rsp, _ := client.Hello(context.Background(), &pb.Request{})
}

Testing

Mock Auth for Tests

import "go-micro.dev/v5/auth/noop"

func TestService(t *testing.T) {
    // Use noop auth for testing (always grants access)
    authProvider := noop.NewAuth()

    service := micro.NewService(
        micro.WrapHandler(
            authWrapper.AuthHandler(authWrapper.HandlerOptions{
                Auth: authProvider,
            }),
        ),
    )

    // Test your service...
}

Generate Test Tokens

func TestWithAuth(t *testing.T) {
    authProvider := noop.NewAuth()

    // Generate test account
    acc, _ := authProvider.Generate("test-user")

    // Generate token
    token, _ := authProvider.Token(
        auth.WithCredentials(acc.ID, acc.Secret),
    )

    // Use token in tests
    client := micro.NewService(
        micro.WrapClient(
            authWrapper.FromToken(token.AccessToken),
        ),
    )
}

Integration with Gateway

If you're using the HTTP gateway (micro server), auth is automatically integrated:

# Gateway enforces auth on HTTP requests
micro server --auth jwt

The gateway:

  1. Extracts Bearer token from HTTP Authorization header
  2. Verifies token
  3. Adds account to metadata
  4. Forwards to service (service still checks with wrapper)

Best Practices

1. Always Use Server Wrapper

Even if using gateway auth, still wrap your services:

// ✅ Good: Defense in depth
micro.WrapHandler(authWrapper.AuthHandler(...))

// ❌ Bad: Only rely on gateway
// (services can be called directly, bypassing gateway)

2. Use Strong Auth in Production

// ✅ Production
authProvider, _ := jwt.NewAuth(
    auth.Issuer("your-company"),
    auth.PrivateKey(privateKey),
    auth.PublicKey(publicKey),
)

// ❌ Development only
authProvider := noop.NewAuth()

3. Scope Your Rules

// ✅ Good: Specific scopes
rules.Grant(&auth.Rule{
    Scope:    "admin",
    Resource: &auth.Resource{Endpoint: "Admin.*"},
})

// ⚠️ Risky: Too broad
rules.Grant(&auth.Rule{
    Scope:    "*",
    Resource: &auth.Resource{Endpoint: "*"},
})

4. Check Account in Handlers

// ✅ Good: Verify account exists
func (s *Service) Delete(ctx context.Context, req *Request, rsp *Response) error {
    acc, ok := auth.AccountFromContext(ctx)
    if !ok || acc.ID != req.UserID {
        return errors.Forbidden("service", "can only delete own data")
    }
    // ...
}

5. Use AuthOptional for Mixed Endpoints

// ✅ Good: Works for both auth and no-auth
func (s *Service) GetProfile(ctx context.Context, req *Request, rsp *Response) error {
    if acc, ok := auth.AccountFromContext(ctx); ok {
        // Authenticated: return private profile
        rsp.Profile = s.getPrivateProfile(acc.ID)
    } else {
        // Public: return limited profile
        rsp.Profile = s.getPublicProfile(req.UserID)
    }
    return nil
}

Troubleshooting

Issue: Handler receives requests without auth

Check:

  1. Is wrapper applied? micro.WrapHandler(authWrapper.AuthHandler(...))
  2. Is endpoint in skip list? Check SkipEndpoints
  3. Is service registered correctly?

Issue: Client gets 401 errors

Check:

  1. Is token valid? Verify with authProvider.Inspect(token)
  2. Is client wrapper applied? micro.WrapClient(authWrapper.FromToken(...))
  3. Is token expired? Check token.Expiry

Issue: Token extraction fails

Check:

  1. Is Authorization header present? md.Get("Authorization")
  2. Is format correct? Must be Bearer <token>
  3. Is metadata propagated? Check context

API Reference

Server Wrapper

  • AuthHandler(opts HandlerOptions) server.HandlerWrapper
  • PublicEndpoints(auth, rules, endpoints) HandlerOptions
  • AuthRequired(auth, rules) HandlerOptions
  • AuthOptional(auth) server.HandlerWrapper

Client Wrapper

  • AuthClient(opts ClientOptions) client.Wrapper
  • FromToken(token) client.Wrapper
  • FromContext(auth) client.Wrapper

Metadata Helpers

  • TokenFromMetadata(md) (string, error)
  • TokenToMetadata(md, token) Metadata
  • AccountFromMetadata(md, auth) (*Account, error)

Constants

  • MetadataKeyAuthorization = "Authorization"
  • BearerPrefix = "Bearer "

Errors

  • ErrMissingToken - No authorization token in metadata
  • ErrInvalidToken - Token format invalid (not "Bearer ")

See Also

License

Apache 2.0