Authentication
API authentication methods and security patterns
GoPie supports multiple authentication methods to secure API access. This guide covers JWT token authentication, API key management, and OAuth2/OIDC flows.
Overview
Authentication Methods
- JWT Tokens: Primary method for web applications
- API Keys: For server-to-server communication
- OAuth2/OIDC: Via Zitadel integration
- Session Tokens: For browser-based authentication
Security Principles
- All API requests must be authenticated
- Tokens have configurable expiration
- Role-based access control (RBAC)
- Organization-level isolation
JWT Authentication
Token Structure
{
"header": {
"alg": "RS256",
"typ": "JWT",
"kid": "key-id-123"
},
"payload": {
"sub": "user_123",
"email": "[email protected]",
"org": "org_456",
"roles": ["developer", "analyst"],
"permissions": [
"datasets:read",
"datasets:write",
"queries:execute"
],
"iat": 1634567890,
"exp": 1634571490,
"iss": "https://auth.gopie.io"
}
}Obtaining Tokens
Login Endpoint
POST /api/v1/auth/login
Content-Type: application/json
{
"email": "[email protected]",
"password": "secure_password"
}Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"user": {
"id": "user_123",
"email": "[email protected]",
"organization_id": "org_456",
"roles": ["developer", "analyst"]
}
}Using Tokens
Authorization Header
GET /api/v1/datasets
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...Token Validation
func ValidateToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(
tokenString,
&Claims{},
func(token *jwt.Token) (interface{}, error) {
return publicKey, nil
},
)
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, ErrInvalidToken
}Token Refresh
Refresh Endpoint
POST /api/v1/auth/refresh
Content-Type: application/json
{
"refresh_token": "eyJhbGciOiJSUzI1NiIs..."
}Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}Token Expiration
- Access Token: 1 hour (configurable)
- Refresh Token: 30 days (configurable)
- Grace Period: 5 minutes for clock skew
API Key Authentication
Key Management
Creating API Keys
POST /api/v1/api-keys
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Content-Type: application/json
{
"name": "Production API Key",
"description": "Key for production data pipeline",
"permissions": [
"datasets:read",
"queries:execute"
],
"expires_at": "2024-12-31T23:59:59Z"
}Response:
{
"id": "key_789",
"key": "gp_live_a1b2c3d4e5f6g7h8i9j0",
"name": "Production API Key",
"created_at": "2024-01-15T10:00:00Z",
"expires_at": "2024-12-31T23:59:59Z",
"permissions": [
"datasets:read",
"queries:execute"
]
}Using API Keys
Header Authentication
GET /api/v1/datasets
X-API-Key: gp_live_a1b2c3d4e5f6g7h8i9j0Query Parameter (Not Recommended)
GET /api/v1/datasets?api_key=gp_live_a1b2c3d4e5f6g7h8i9j0Key Rotation
POST /api/v1/api-keys/{key_id}/rotate
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...Response:
{
"old_key": "gp_live_a1b2c3d4e5f6g7h8i9j0",
"new_key": "gp_live_k1l2m3n4o5p6q7r8s9t0",
"grace_period_ends": "2024-01-22T10:00:00Z"
}OAuth2/OIDC Integration
Zitadel Configuration
Discovery Endpoint
https://auth.gopie.io/.well-known/openid-configurationOAuth2 Flow
Client Configuration
Authorization Request
GET https://auth.gopie.io/oauth/authorize?
client_id=your_client_id&
redirect_uri=https://yourapp.com/callback&
response_type=code&
scope=openid profile email datasets:read&
state=random_state_valueToken Exchange
POST https://auth.gopie.io/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=authorization_code_here&
client_id=your_client_id&
client_secret=your_client_secret&
redirect_uri=https://yourapp.com/callbackScopes and Permissions
Available Scopes
Basic:
- openid: OpenID Connect support
- profile: User profile information
- email: User email address
- offline_access: Refresh token issuance
Resource Scopes:
- datasets:read: Read dataset information
- datasets:write: Create/update datasets
- datasets:delete: Delete datasets
- queries:execute: Execute queries
- organizations:manage: Manage organization
Admin Scopes:
- admin:users: Manage all users
- admin:billing: Access billing information
- admin:system: System administrationRole-Based Access Control
Role Hierarchy
Roles:
owner:
inherits: [admin]
permissions:
- organizations:delete
- billing:manage
admin:
inherits: [developer]
permissions:
- organizations:manage
- users:manage
- settings:manage
developer:
inherits: [analyst]
permissions:
- datasets:write
- datasets:delete
- api-keys:manage
analyst:
inherits: [viewer]
permissions:
- queries:execute
- datasets:read
- visualizations:create
viewer:
permissions:
- datasets:list
- queries:read
- dashboards:viewPermission Checking
func RequirePermission(permission string) fiber.Handler {
return func(c *fiber.Ctx) error {
claims := c.Locals("claims").(*Claims)
if !hasPermission(claims, permission) {
return c.Status(403).JSON(fiber.Map{
"error": "Insufficient permissions",
"required": permission,
})
}
return c.Next()
}
}
// Usage
app.Post("/api/v1/datasets",
RequireAuth(),
RequirePermission("datasets:write"),
datasetHandler.Create,
)Security Headers
Required Headers
func SecurityHeaders() fiber.Handler {
return func(c *fiber.Ctx) error {
c.Set("X-Content-Type-Options", "nosniff")
c.Set("X-Frame-Options", "DENY")
c.Set("X-XSS-Protection", "1; mode=block")
c.Set("Strict-Transport-Security", "max-age=31536000")
c.Set("Content-Security-Policy", "default-src 'self'")
return c.Next()
}
}CORS Configuration
app.Use(cors.New(cors.Config{
AllowOrigins: []string{
"https://app.gopie.io",
"http://localhost:3000",
},
AllowMethods: []string{
"GET", "POST", "PUT", "DELETE", "OPTIONS",
},
AllowHeaders: []string{
"Origin", "Content-Type", "Accept",
"Authorization", "X-API-Key",
},
AllowCredentials: true,
MaxAge: 86400,
}))Error Responses
Authentication Errors
// 401 Unauthorized
{
"error": "unauthorized",
"message": "Invalid or missing authentication credentials",
"code": "AUTH_REQUIRED"
}
// 403 Forbidden
{
"error": "forbidden",
"message": "Insufficient permissions for this operation",
"required_permission": "datasets:write",
"code": "PERMISSION_DENIED"
}
// Token Expired
{
"error": "token_expired",
"message": "The access token has expired",
"code": "TOKEN_EXPIRED"
}Best Practices
Security Guidelines
-
Token Storage
- Never store tokens in localStorage
- Use httpOnly cookies for web apps
- Implement secure token storage for mobile
-
API Key Management
- Rotate keys regularly
- Use different keys per environment
- Monitor key usage
-
Permission Design
- Follow principle of least privilege
- Regular permission audits
- Document permission requirements
Implementation Tips
// TypeScript API client with auth
class GoPieClient {
private token: string | null = null;
async authenticate(email: string, password: string) {
const response = await fetch('/api/v1/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const data = await response.json();
this.token = data.access_token;
// Set up automatic refresh
this.scheduleTokenRefresh(data.expires_in);
}
async request(path: string, options: RequestInit = {}) {
const headers = {
...options.headers,
'Authorization': `Bearer ${this.token}`,
};
const response = await fetch(path, { ...options, headers });
if (response.status === 401) {
await this.refreshToken();
// Retry request
return fetch(path, { ...options, headers });
}
return response;
}
}Testing Authentication
Test Credentials
# Development environment only
test_users:
admin:
email: [email protected]
password: test_admin_123
roles: [admin]
developer:
email: [email protected]
password: test_dev_123
roles: [developer]
analyst:
email: [email protected]
password: test_analyst_123
roles: [analyst]cURL Examples
# Login
curl -X POST https://api.gopie.io/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"password"}'
# Use token
curl https://api.gopie.io/api/v1/datasets \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."
# API key
curl https://api.gopie.io/api/v1/datasets \
-H "X-API-Key: gp_live_a1b2c3d4e5f6g7h8i9j0"Next Steps
- Explore Datasets API for data management
- Learn about Queries API for executing queries
- Review Organizations API for team management