# JWT Service-to-Service Authentication

## Overview

Services authenticate with each other using JWT tokens. The system uses **two-factor authentication**: JWT signature validation + user credential validation against the database.

---

## How It Works

### Outgoing Request (Skeleton → QApix)

```
1. Skeleton wants to call QApix
2. Creates JWT with:
   - iss: "Skeleton"
   - token: "franco:bpa31kniubroq28rpvg010" (from External.QApix.Token in config)
   - iat: current timestamp
3. Signs JWT with QAPIX_QUANTEX_SECRET_KEY
4. Sends request with header: Authorization: {JWT}
```

### Incoming Request (QApix → Skeleton)

```
1. Skeleton receives JWT from QApix
2. Validates JWT signature with SKELETON_QUANTEX_SECRET_KEY
3. Checks "QApix" is in AuthorizedServices config
4. Checks QApix has required permissions (currently FullAccess)
5. Extracts token claim: "franco:bpa31kniubroq28rpvg010"
6. Looks up user "franco" in database
7. Verifies password "bpa31kniubroq28rpvg010" matches
8. ✅ Request allowed
```

---

## Configuration

### conf.toml

```toml
# Enable/disable JWT authentication
EnableJWTAuth = true  # Set to true to enable JWT authentication checks

# Who we call (outgoing requests)
[External.QApix]
Name = "QApix"
Token = "franco:bpa31kniubroq28rpvg010"  # Credentials we use to authenticate
Host = "https://qapix.example.com"
Port = "5001"

# Who can call us (incoming requests)
[AuthorizedServices.QApix]
Name = "QApix"
Permissions = ["FullAccess"]  # What QApix is allowed to do
# Token validated against user DB - not stored in config
```

**Configuration Options:**
- `EnableJWTAuth` (bool): Controls whether JWT authentication is enabled
  - `true`: Enables JWT authentication and health check validation
  - `false`: Disables JWT authentication features (default for local development)

### Environment Variables

```bash
# On Skeleton
export SKELETON_QUANTEX_SECRET_KEY="..."  # Validates incoming JWTs
export QAPIX_QUANTEX_SECRET_KEY="..."     # Signs outgoing JWTs to QApix

# On QApix
export QAPIX_QUANTEX_SECRET_KEY="..."     # Validates incoming JWTs
export SKELETON_QUANTEX_SECRET_KEY="..."  # Signs outgoing JWTs to Skeleton
```

**Important**: Secret keys must match across services:
- `SKELETON_QUANTEX_SECRET_KEY` on Skeleton = `SKELETON_QUANTEX_SECRET_KEY` on QApix
- `QAPIX_QUANTEX_SECRET_KEY` on Skeleton = `QAPIX_QUANTEX_SECRET_KEY` on QApix

---

## Security Layers

| Layer | Description | Purpose |
|-------|-------------|---------|
| 1. JWT Signature | Validates HS256 signature | Proves caller has secret key |
| 2. Issuer Whitelist | Checks AuthorizedServices | Only known services allowed |
| 3. Permission Check | Validates service permissions | ReadOnly/ReadWrite/FullAccess |
| 4. User Validation | Checks token against user DB | Real credentials required |

---

## Code Flow

### Creating JWT
**Location**: `src/common/jwttoken/jwt.go`

```go
func Encrypt(auth app.ExtAuth) (string, error)
```

**Usage**:
```go
auth := app.ExtAuth{
    Name: "QApix",
    Token: "franco:bpa31kniubroq28rpvg010",
}
jwt, err := jwttoken.Encrypt(auth)
// Returns signed JWT token
```

### Validating JWT
**Location**: `src/common/jwttoken/jwt.go`

```go
func Validate(token string, authServices map[string]AuthorizedService) (*AuthorizedService, error)
```

**Usage**:
```go
service, err := jwttoken.Validate(jwtToken, config.AuthorizedServices)
// Returns authorized service with token and permissions
```

### Middleware
**Location**: `src/client/api/rest/midlewares.go`

```go
func (cont *Controller) JWTTokenAuth(reqToken string, ctx *gin.Context)
```

**Flow**:
1. Receives JWT from Authorization header
2. Calls `jwttoken.Validate()` to verify JWT and check permissions
3. Calls `validateServiceToken(token)` to verify user credentials in database
4. Sets `authorized_service` in Gin context
5. Proceeds to next handler

---

## JWT Token Structure

### Claims

```json
{
  "iss": "Skeleton",                    // Issuer (who created the token)
  "token": "franco:bpa31kniubroq28rpvg010",  // User credentials
  "iat": 1704481490                     // Issued at (Unix timestamp)
}
```

### Header

```json
{
  "alg": "HS256",                       // Algorithm (HMAC SHA-256)
  "typ": "JWT"                          // Type
}
```

---

## Service Permissions

Defined in `src/app/model.go`:

```go
type ServicePermission int

const (
    ServicePermissionReadOnly    // Read-only access
    ServicePermissionReadWrite   // Read and write access
    ServicePermissionFullAccess  // Full access to all operations
    ServicePermissionUndefined   // No permissions
)
```

**Permission Check**:
```go
service.HasPermissions(app.ServicePermissionFullAccess) // Returns bool
```

---

## Key Benefits

- ✅ **No credentials in config** - Token validated against user database
- ✅ **Recipient controls access** - Permissions defined by recipient, not sender
- ✅ **Two-factor auth** - Secret key + user credentials
- ✅ **Easy rotation** - Change user password → JWT auth automatically updates
- ✅ **Audit trail** - Know which service user made each request
- ✅ **Single source of truth** - User credentials only in database
- ✅ **Configurable** - Enable/disable JWT auth per environment via `EnableJWTAuth`

---

## Testing

### Generate JWT Token

```bash
# Set environment variable
export SKELETON_QUANTEX_SECRET_KEY="your-secret-key"

# Run generator
cd tools
./generate-jwt.sh

# Follow prompts:
# Issuer: Skeleton
# Service: SKELETON
# Token: franco:bpa31kniubroq28rpvg010
```

### Test with curl

```bash
# Store generated token
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# Test endpoint
curl -H "Authorization: $TOKEN" \
     http://localhost:8097/api/v1/endpoint

# Expected: 200 OK (if token valid and user exists)
# Expected: 401 Unauthorized (if token invalid or user not found)
```

### Test Scenarios

| Scenario | Expected Result |
|----------|-----------------|
| Valid JWT + Valid user | ✅ 200 OK |
| Valid JWT + Invalid user | ❌ 401 Unauthorized |
| Invalid JWT signature | ❌ 401 Unauthorized |
| Unknown issuer | ❌ 401 Unauthorized |
| Insufficient permissions | ❌ 403 Forbidden |

---

## Authentication Flow Diagram

```
┌─────────────┐                                    ┌─────────────┐
│  Skeleton   │                                    │    QApix    │
└──────┬──────┘                                    └──────┬──────┘
       │                                                  │
       │ 1. Create JWT                                   │
       │    - iss: "Skeleton"                            │
       │    - token: "franco:password"                   │
       │                                                  │
       │ 2. Sign with QAPIX_QUANTEX_SECRET_KEY          │
       │                                                  │
       │ 3. HTTP Request                                 │
       │    Authorization: {JWT}                         │
       ├─────────────────────────────────────────────────>│
       │                                                  │
       │                                    4. Decrypt JWT│
       │                    (QAPIX_QUANTEX_SECRET_KEY)   │
       │                                                  │
       │                              5. Validate claims  │
       │                                    (iss, token)  │
       │                                                  │
       │                         6. Check AuthorizedServices│
       │                                 ("Skeleton" allowed?)│
       │                                                  │
       │                          7. Check permissions   │
       │                              (FullAccess?)      │
       │                                                  │
       │                       8. Validate user in DB    │
       │                          (franco:password exists?)│
       │                                                  │
       │ 9. Response (200 OK or 401/403)                 │
       │<─────────────────────────────────────────────────┤
       │                                                  │
```

---

## Troubleshooting

### Common Issues

**"Invalid JWT"**
- Check secret key environment variable is set
- Verify secret keys match on both services

**"Service not authorized"**
- Add service to `AuthorizedServices` in conf.toml
- Verify `Name` matches JWT `iss` claim

**"Invalid credentials"**
- Check user exists in database
- Verify token format is `email:password`
- Ensure password matches user record

**"Insufficient permissions"**
- Check service has required permission in `AuthorizedServices`
- Update `Permissions` array in conf.toml

---

## Files Reference

| File | Purpose |
|------|---------|
| `src/common/jwttoken/jwt.go` | JWT encryption, decryption, validation |
| `src/client/api/rest/midlewares.go` | HTTP middleware for JWT authentication |
| `src/client/api/rest/controller.go` | REST controllers including health check with JWT validation |
| `src/app/model.go` | AuthorizedService, ServicePermission types, EnableJWTAuth config |
| `src/client/api/rest/server.go` | REST API server configuration |
| `src/cmd/service/service.go` | Service runner that initializes JWT config |
| `conf.toml` | Service configuration |
| `tools/generate-jwt.sh` | JWT token generator for testing |

---

## Migration Notes

### From Old System

**Before** (self-declared permissions):
```toml
[External.QApix]
Token = "franco:password"
Permissions = ["QApixAccess"]  # ❌ Sender declares own permissions
```

**After** (recipient-controlled):
```toml
[External.QApix]
Token = "franco:password"
# ✅ No permissions - recipient decides

[AuthorizedServices.QApix]
Permissions = ["FullAccess"]  # ✅ Recipient controls access
```

### Deployment Steps

1. Update `conf.toml` on both services
   - Set `EnableJWTAuth = true` for production environments
   - Set `EnableJWTAuth = false` for local development
   - Configure `External` and `AuthorizedServices` sections
2. Set environment variables for secret keys
3. Restart services
4. Test with `generate-jwt.sh`
5. Verify health endpoint shows `jwtAuthentications: "ok"`
6. Monitor logs for authentication errors

---

## Security Best Practices

1. **Rotate Secret Keys Regularly** - Update environment variables periodically
2. **Use Strong Secrets** - Minimum 32 bytes, cryptographically random
3. **HTTPS Only** - Never send JWTs over unencrypted connections
4. **Monitor Auth Logs** - Track failed authentication attempts
5. **Principle of Least Privilege** - Use ReadOnly when possible
6. **Separate Service Users** - Don't share user credentials across services

---

## Health Check Endpoint

The `/health` endpoint provides service health status and optionally includes JWT authentication validation when enabled.

### Response Structure

**When `EnableJWTAuth = false`** (default for local development):
```json
{
  "status": "ok",
  "build": "feature-branch",
  "sha": "abc123def"
}
```

**When `EnableJWTAuth = true`** (production):
```json
{
  "status": "ok",
  "build": "feature-branch",
  "sha": "abc123def",
  "jwtAuthentications": "ok"
}
```

**When JWT validation fails** (`EnableJWTAuth = true`):
```json
{
  "status": "degraded",
  "build": "feature-branch",
  "sha": "abc123def",
  "jwtAuthentications": "error"
}
```

### Health Check Validation

When `EnableJWTAuth = true`, the health endpoint validates JWT authentication by:
1. Attempting to fetch a test user from the database
2. Verifying the database connection is working
3. Returning `jwtAuthentications: "ok"` if successful
4. Returning `jwtAuthentications: "error"` and `status: "degraded"` if validation fails

**Location**: `src/client/api/rest/controller.go:178`

---

## Additional Resources

- [JWT RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)
- [golang-jwt/jwt](https://github.com/golang-jwt/jwt)
- Internal: `tools/README-JWT-GENERATOR.md`
