Skip to main content

Auth Service — Getting Started

This guide gets you connected to the Auth service and making your first calls in 10 minutes.


What is Auth?

The Auth service handles authentication — proving that a user is who they claim to be. It manages email/password credentials, OAuth provider connections (Google, GitHub, Discord), JWT issuance, and refresh token lifecycle.

Auth delegates authorization (users, tenants, roles, permissions) to the IAM service. Auth proves identity. IAM decides what that identity can do.


Connection Setup

Import the generated stubs

Add the Auth module as a dependency:

go get github.com/TopengDev/aenoxa_auth@latest

Create a gRPC connection

package main

import (
"log"

authv1 "github.com/TopengDev/aenoxa_auth/gen/auth/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

func main() {
conn, err := grpc.NewClient("localhost:50052",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()

authClient := authv1.NewAuthServiceClient(conn)
healthClient := authv1.NewHealthServiceClient(conn)
}

Quick Walkthrough

1. Register a new user

resp, err := authClient.Register(ctx, &authv1.RegisterRequest{
Email: "user@example.com",
Password: "SecurePass123!",
IdempotencyKey: "reg-001",
})
// resp.Tokens.AccessToken — user-scoped JWT (15 min)
// resp.Tokens.RefreshToken — opaque refresh token (30 days)
// resp.UserId — IAM user ID

2. Log in

resp, err := authClient.Login(ctx, &authv1.LoginRequest{
Email: "user@example.com",
Password: "SecurePass123!",
})

3. Select a tenant

The access token from login/register is user-scoped — it contains sub (user ID) and email but no tenant context. To get a tenant-scoped token, call SelectTenant:

import "google.golang.org/grpc/metadata"

// Attach the user-scoped JWT
ctx := metadata.AppendToOutgoingContext(ctx, "authorization", "bearer "+resp.Tokens.AccessToken)

tenantResp, err := authClient.SelectTenant(ctx, &authv1.SelectTenantRequest{
TenantId: "your-tenant-uuid",
})
// tenantResp.AccessToken — tenant-scoped JWT with permissions

4. Refresh tokens

When the access token expires, use the refresh token to get a new pair:

refreshResp, err := authClient.RefreshToken(ctx, &authv1.RefreshTokenRequest{
RefreshToken: resp.Tokens.RefreshToken,
})
// refreshResp.Tokens.AccessToken — new access token
// refreshResp.Tokens.RefreshToken — new refresh token (old one is now invalid)

5. Log out

_, err := authClient.Logout(ctx, &authv1.LogoutRequest{
RefreshToken: refreshToken,
})

This revokes the refresh token family server-side. The client app should also discard its stored tokens locally.


Authentication

Most Auth endpoints are public (Register, Login, OAuth, RefreshToken, Logout). Four endpoints require a user-scoped JWT in the authorization header:

EndpointAuth Required
SelectTenantYes
ListUserTenantsYes
LinkOAuthYes
UnlinkOAuthYes

Pass the JWT via gRPC metadata:

ctx := metadata.AppendToOutgoingContext(ctx, "authorization", "bearer "+accessToken)
resp, err := authClient.ListUserTenants(ctx, &authv1.ListUserTenantsRequest{})

Testing with grpcurl

# Health check
grpcurl -plaintext localhost:50052 auth.v1.HealthService/Check

# Register
grpcurl -plaintext -d '{
"email": "user@example.com",
"password": "SecurePass123!",
"idempotency_key": "reg-001"
}' localhost:50052 auth.v1.AuthService/Register

# Login
grpcurl -plaintext -d '{
"email": "user@example.com",
"password": "SecurePass123!"
}' localhost:50052 auth.v1.AuthService/Login

# Refresh token
grpcurl -plaintext -d '{
"refresh_token": "<raw_refresh_token>"
}' localhost:50052 auth.v1.AuthService/RefreshToken

# List tenants (requires JWT)
grpcurl -plaintext \
-H "authorization: bearer <access_token>" \
localhost:50052 auth.v1.AuthService/ListUserTenants

# Select tenant (requires JWT)
grpcurl -plaintext \
-H "authorization: bearer <access_token>" \
-d '{"tenant_id": "<uuid>"}' \
localhost:50052 auth.v1.AuthService/SelectTenant

# Logout
grpcurl -plaintext -d '{
"refresh_token": "<raw_refresh_token>"
}' localhost:50052 auth.v1.AuthService/Logout

Client Setup Pattern

Create the Auth connection once at startup and share it:

type AuthClient struct {
conn *grpc.ClientConn
Auth authv1.AuthServiceClient
Health authv1.HealthServiceClient
}

func NewAuthClient(addr string) (*AuthClient, error) {
conn, err := grpc.NewClient(addr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
return nil, err
}

return &AuthClient{
conn: conn,
Auth: authv1.NewAuthServiceClient(conn),
Health: authv1.NewHealthServiceClient(conn),
}, nil
}

func (c *AuthClient) Close() error {
return c.conn.Close()
}

Error Handling

Auth returns standard gRPC status codes:

CodeMeaningAction
OKSuccessProceed normally
INVALID_ARGUMENTBad input (empty email, weak password)Don't retry — fix input
ALREADY_EXISTSEmail already registeredShow user-facing message
UNAUTHENTICATEDBad credentials, expired/revoked tokenRedirect to login
NOT_FOUNDResource not found (tenant, membership)Check the ID
FAILED_PRECONDITIONInvalid state (e.g., can't unlink last auth method)Show user-facing message
INTERNALServer errorLog and alert
import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

resp, err := authClient.Login(ctx, req)
if err != nil {
st, _ := status.FromError(err)
switch st.Code() {
case codes.Unauthenticated:
// Invalid email or password
case codes.InvalidArgument:
// Bad input
default:
log.Printf("auth error: %s: %s", st.Code(), st.Message())
}
}

Next Steps

  • API Reference — Complete endpoint documentation
  • Token Flow — Two-phase JWT model, refresh rotation, claims structure
  • OAuth Guide — Setting up and using OAuth providers
  • Event Catalog — Consuming auth domain events via RabbitMQ