Go SDK for Identity and Access Management — Authentication, Authorization, and Multi-tenancy client library.
iam-go is a Go SDK for integrating with any IAM server that implements the standard Identity and Access Management capabilities. It enables any Go service to:
- Verify JWT tokens locally via JWKS (RS256 public key) — no network calls
- Check permissions with local caching
- OAuth2 Client Credentials for service-to-service authentication
- Inject tenant context automatically via middleware
The SDK is backend-agnostic — all services are defined as interfaces. Concrete implementations (gRPC, REST, in-memory) are injected via the Option pattern.
What is a "Standard IAM Server"?
Any IAM server that implements the P0 Requirements (see Roadmap):
- P0.1: RS256 JWT signing + JWKS endpoint
- P0.2: OAuth2 Client Credentials Grant (service-to-service)
- P0.3: IAM service API for external token verification and permission checking
Once your IAM server implements these, any service using
iam-gocan authenticate and authorize without vendor lock-in.
Architecture: Proto-first contracts, framework-agnostic middleware
The same Auth / Tenant / Require / RequireAny middleware stack is provided for each framework, in two tiers:
| Framework | Package | Example |
|---|---|---|
| Kratos (HTTP + gRPC) | middleware/kratosmw/ |
examples/kratos-service |
| Pure gRPC | middleware/grpcmw/ |
examples/grpc-service |
New downstream services should be proto-first Kratos. Contracts live in proto/iam/v1/; Kratos middleware covers both HTTP and gRPC transports.
| Framework | Package | Example |
|---|---|---|
Standard net/http (also Chi, Echo via adapters) |
middleware/httpmw/ |
examples/std-http-service |
| Gin | middleware/ginmw/ |
examples/gin-service |
These exist so existing services (legacy gateways, one-off tools) can adopt official IAM middleware without a framework rewrite. Don't start new projects on this tier.
All four share the same core logic and error semantics; only the framework adapter differs.
ginmw is a separate Go module so that gin (and its ~20 transitive dependencies) never enters the dependency tree of Kratos/gRPC-only services:
go get github.com/chimerakang/iam-go/middleware/ginmwThe root
go get github.com/chimerakang/iam-gostays gin-free.
Your Service (Kratos / net/http / Gin / gRPC)
│
├── <mw>.Auth(client) ← JWT verification (local, via JWKS)
├── <mw>.Tenant(client) ← Tenant context injection
├── <mw>.Require(client, p) ← Permission check
│ (kratosmw / httpmw / ginmw / grpcmw)
└── client.Authz().Check() ← Direct permission query
client.Users().GetCurrent()
client.OAuth2().GetCachedToken() ← OAuth2 M2M token
go get github.com/chimerakang/iam-goimport "github.com/chimerakang/iam-go/valhalla"
client, err := valhalla.New("iam.example.com:50051",
valhalla.WithOAuth2(os.Getenv("IAM_CLIENT_ID"), os.Getenv("IAM_CLIENT_SECRET")), // optional M2M
)
// JWKS verifier, cached authorizer, user/tenant/session/auth services — all wired.package main
import (
iam "github.com/chimerakang/iam-go"
"github.com/chimerakang/iam-go/middleware/kratosmw"
"github.com/go-kratos/kratos/v2/transport/http"
)
func main() {
// Initialize IAM client with injected implementations
client, err := iam.NewClient(
iam.Config{
Endpoint: "iam-server:9000",
JWKSUrl: "https://auth.example.com/.well-known/jwks.json",
OAuth2ClientID: os.Getenv("IAM_OAUTH2_CLIENT_ID"),
OAuth2ClientSecret: os.Getenv("IAM_OAUTH2_CLIENT_SECRET"),
OAuth2TokenURL: os.Getenv("IAM_OAUTH2_TOKEN_URL"),
},
iam.WithTokenVerifier(myVerifier),
iam.WithAuthorizer(myAuthz),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Kratos HTTP server with IAM middleware
httpSrv := http.NewServer(
http.Middleware(
kratosmw.Auth(client),
kratosmw.Tenant(client),
),
)
}| Package | Description |
|---|---|
iam-go (root) |
Client, Config, Option pattern, interfaces, domain types, context helpers |
middleware/kratosmw/ |
Kratos middleware — Auth, Tenant, Require (HTTP + gRPC) |
middleware/httpmw/ |
Standard library net/http middleware — same stack, plus Chain |
middleware/ginmw/ |
Gin middleware — same stack as gin.HandlerFunc (separate module: go get github.com/chimerakang/iam-go/middleware/ginmw) |
middleware/grpcmw/ |
Pure gRPC interceptors (for non-Kratos services) |
authz/ |
Local permission checking — claims-based ClaimsChecker (zero network) and caching Authorizer; see docs/PERMISSION_CHECKING.md |
jwks/ |
JWKS-based TokenVerifier (standard RFC 7517) |
fake/ |
In-memory implementations for testing |
proto/iam/v1/ |
Proto service definitions and generated gRPC stubs |
The root package defines these interfaces — implement them to integrate with any IAM backend:
| Interface | Purpose |
|---|---|
TokenVerifier |
Verify tokens, extract claims |
Authorizer |
Check permissions (with caching) |
UserService |
User CRUD and role queries |
TenantService |
Tenant resolution and membership |
SessionService |
Session management |
OAuth2TokenExchanger |
OAuth2 client credentials token exchange |
AuthService |
User login (email/password + social OAuth) |
// Middleware verifies JWT via any JWKS-compliant endpoint — pick your framework:
kratosmw.Auth(client) // Kratos
mux.Handle("/api/", httpmw.Auth(client)(handler)) // net/http
r.Use(ginmw.Auth(client)) // Gin// Service-to-service authentication via OAuth2 token
kratosmw.OAuth2ClientCredentials(client)resp, err := client.Auth().Login(ctx, iam.LoginRequest{
Email: "user@example.com",
Password: "secret",
TenantID: "tenant-uuid",
AppID: "my_app",
})// Backend-for-Frontend: backend exchanges authorization code for id_token,
// then calls IAM to get a Valhalla JWT.
resp, err := client.Auth().SocialLogin(ctx, iam.SocialLoginRequest{
Provider: "line", // "google", "apple", "line"
IDToken: lineIDToken, // obtained server-side from provider
Nonce: sessionNonce, // replay prevention (required for Apple / LINE)
AppID: "hospital_erp_mobile",
TenantID: "tenant-uuid",
})
// resp.Tokens.AccessToken — Valhalla JWT ready to useService contracts are defined in proto/iam/v1/iam.proto. Generate Go stubs with:
make proto # Generate gRPC stubs
make proto-lint # Lint proto filesUse the fake package for unit tests without a real IAM server:
import "github.com/chimerakang/iam-go/fake"
func TestMyHandler(t *testing.T) {
client := fake.NewClient(
fake.WithUser("user1", "tenant1", "user1@test.com", []string{"admin"}),
fake.WithPermissions("user1", []string{"users:read"}),
fake.WithSocialLogin("line", &iam.User{
ID: "user1",
Email: "user@example.com",
TenantID: "tenant1",
}, &iam.TokenPair{
AccessToken: "fake-jwt",
TokenType: "Bearer",
ExpiresIn: 3600,
}),
)
ctx := fake.ContextWithUserID(context.Background(), "user1")
ok, _ := client.Authz().Check(ctx, "users:read")
// ok == true
// Test social login
resp, _ := client.Auth().SocialLogin(ctx, iam.SocialLoginRequest{
Provider: "line",
IDToken: "any-token",
AppID: "test_app",
})
// resp.Tokens.AccessToken == "fake-jwt"
}make build # go build ./...
make test # go test ./...
make lint # go vet ./...
make proto # buf generateMIT License - see LICENSE for details.