Service Authentication
Authenticating Deployed Services
DownToZero (DTZ) provides built-in authentication for your deployed services, making it easy to build secure applications without managing your own authentication infrastructure.
Overview
When you deploy a service to DTZ, the platform automatically provides authentication credentials and context information through environment variables. Your service can use these to:
- Authenticate requests to other DTZ services (API calls, object storage, etc.)
- Verify incoming user requests
- Access context-specific resources
- Implement secure service-to-service communication
Environment Variables
DTZ automatically injects the following environment variables into your service containers:
Variable | Description | Example |
---|---|---|
DTZ_ACCESS_TOKEN |
A JWT token for accessing DTZ services within your context. | eyJhbGciOiJSUzI1NiI... |
DTZ_CONTEXT_ID |
Your DTZ context identifier. | context-3cd84429-64a4-4226-b868-c83feeff0f46 |
PORT |
The port your service should listen on. | 80 |
Authenticating Incoming Requests
Your deployed service can authenticate incoming requests using several methods:
API Key Authentication
Users can authenticate with your service using DTZ API keys by providing the key in the X-API-KEY
header.
curl -H "X-API-KEY: your-api-key" https://yourservice.dtz.rocks/api/endpoint
Bearer Token Authentication
Provide a JWT token in the Authorization
header to authenticate.
curl -H "Authorization: Bearer your-jwt-token" https://yourservice.dtz.rocks/api/endpoint
Basic Authentication
You can also pass API keys using basic authentication.
curl -u apikey:your-api-key https://yourservice.dtz.rocks/api/endpoint
Cookie-based Authentication
For web applications, the DTZ Identity service can handle authentication through browser cookies.
OAuth Flow
DTZ provides automatic OAuth authentication for web applications. When unauthenticated users access your service, they are automatically redirected to the DTZ login page and then redirected back after a successful login.
Using DTZ Authentication in Your Service
Making Authenticated Requests to DTZ Services
Use the DTZ_ACCESS_TOKEN
environment variable to make authenticated calls to other DTZ services.
import os
import requests
# Get the DTZ access token from the environment
token = os.environ.get('DTZ_ACCESS_TOKEN')
context_id = os.environ.get('DTZ_CONTEXT_ID')
# Make an authenticated request to the DTZ API
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
response = requests.get(
'https://api.dtz.rocks/v1/containers/services',
headers=headers
)
// Node.js example
const token = process.env.DTZ_ACCESS_TOKEN;
const contextId = process.env.DTZ_CONTEXT_ID;
const response = await fetch('https://api.dtz.rocks/v1/containers/services', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
# Bash example
curl -H "Authorization: Bearer $DTZ_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
https://api.dtz.rocks/v1/containers/services
Verifying Incoming Authentication
DTZ automatically handles authentication for incoming requests. When a user makes an authenticated request to your service, DTZ:
- Validates the authentication credentials.
- Converts API keys to JWT tokens.
- Forwards the request with an
Authorization: Bearer <token>
header.
Your service receives the JWT token and can extract user information from it.
import jwt
import os
from flask import Flask, request
app = Flask(__name__)
@app.route('/protected')
def protected_endpoint():
auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return {'error': 'No authentication provided'}, 401
token = auth_header.split(' ')[1]
try:
# The token is already verified by DTZ, so you can
# extract claims without signature verification.
payload = jwt.decode(token, options={"verify_signature": False})
user_id = payload.get('sub') # Identity ID
context_id = payload.get('scope') # Context ID
roles = payload.get('roles', []) # User roles
return {
'user_id': user_id,
'context_id': context_id,
'roles': roles,
'message': 'Access granted'
}
except jwt.InvalidTokenError:
return {'error': 'Invalid token'}, 401
// Express.js example
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
app.get('/protected', (req, res) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No authentication provided' });
}
const token = authHeader.split(' ')[1];
try {
// The token is already verified by DTZ.
const payload = jwt.decode(token);
const userId = payload.sub; // Identity ID
const contextId = payload.scope; // Context ID
const roles = payload.roles || []; // User roles
res.json({
user_id: userId,
context_id: contextId,
roles: roles,
message: 'Access granted'
});
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
});
JWT Token Structure
DTZ JWT tokens contain the following claims:
Claim | Description | Example |
---|---|---|
iss |
Issuer (always “dtz.rocks”) | "dtz.rocks" |
sub |
Subject (user identity ID) | "identity-abc123..." |
aud |
Audience (always “dtz.rocks”) | "dtz.rocks" |
scope |
Context ID | "context-3cd84429..." |
roles |
User roles/permissions | ["https://dtz.rocks/context/admin/{context_id}"] |
contexts |
Available contexts | ["context-3cd84429..."] |
exp |
Expiration time | 1640995200 |
iat |
Issued at time | 1640908800 |
Role-Based Access Control
DTZ uses role-based access control with URI-based role identifiers. Common role patterns include:
https://dtz.rocks/context/admin/{context_id}
- Context administratorhttps://dtz.rocks/containers/admin/{context_id}
- Container service administratorhttps://dtz.rocks/objectstore/admin/{context_id}
- Object store administrator
You can check for roles in your service:
def check_role(token, required_role_pattern):
payload = jwt.decode(token, options={"verify_signature": False})
roles = payload.get('roles', [])
context_id = payload.get('scope')
required_role = required_role_pattern.replace('{context_id}', context_id)
return required_role in roles
# Example:
if check_role(token, 'https://dtz.rocks/containers/admin/{context_id}'):
# User has container admin permissions
pass
Best Practices
Security
- Always validate that JWT tokens contain the expected claims.
- Check user roles before granting access to sensitive operations.
- Use HTTPS for all communications.
- Do not log sensitive authentication tokens.
Error Handling
- Return appropriate HTTP status codes (e.g., 401 for unauthorized, 403 for forbidden).
- Provide meaningful error messages without exposing sensitive information.
Performance
- Cache JWT token validation results when possible.
- Use connection pooling for DTZ API calls.
- Consider implementing request rate limiting.
Example: Complete Authenticated Service
Here is a complete example of a Python Flask service with DTZ authentication:
import os
import jwt
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
DTZ_TOKEN = os.environ.get('DTZ_ACCESS_TOKEN')
DTZ_CONTEXT_ID = os.environ.get('DTZ_CONTEXT_ID')
def get_user_from_token(token):
"""Extracts user information from a DTZ JWT token."""
try:
payload = jwt.decode(token, options={"verify_signature": False})
return {
'user_id': payload.get('sub'),
'context_id': payload.get('scope'),
'roles': payload.get('roles', [])
}
except jwt.InvalidTokenError:
return None
def require_auth(f):
"""A decorator to require authentication."""
def decorated(*args, **kwargs):
auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return jsonify({'error': 'Authentication required'}), 401
token = auth_header.split(' ')[1]
user = get_user_from_token(token)
if not user:
return jsonify({'error': 'Invalid token'}), 401
request.user = user
return f(*args, **kwargs)
decorated.__name__ = f.__name__
return decorated
@app.route('/health')
def health():
"""A public health check endpoint."""
return jsonify({'status': 'healthy'})
@app.route('/profile')
@require_auth
def profile():
"""A protected endpoint that returns the user's profile."""
return jsonify({
'user_id': request.user['user_id'],
'context_id': request.user['context_id'],
'roles': request.user['roles']
})
@app.route('/admin/users')
@require_auth
def admin_users():
"""An admin-only endpoint."""
required_role = f'https://dtz.rocks/context/admin/{DTZ_CONTEXT_ID}'
if required_role not in request.user['roles']:
return jsonify({'error': 'Admin access required'}), 403
# Make an authenticated request to the DTZ API
headers = {'Authorization': f'Bearer {DTZ_TOKEN}'}
response = requests.get(
'https://identity.dtz.rocks/api/2021-02-21/users',
headers=headers
)
return jsonify(response.json())
if __name__ == '__main__':
port = int(os.environ.get('PORT', 80))
app.run(host='0.0.0.0', port=port)
Troubleshooting
Common Issues
- Authentication token not found: Ensure your service is deployed through the DTZ containers service and that the environment variables are being read correctly.
- Invalid token errors: Check that you are correctly extracting the token from the
Authorization
header and that the JWT parsing is working. - 403 Forbidden errors: Verify that the user has the required roles in their JWT token.
- Service-to-service authentication failing: Ensure you are using the
DTZ_ACCESS_TOKEN
environment variable for outbound requests.
Testing Authentication
You can test your service’s authentication using curl
:
# Test with an API key
curl -H "X-API-KEY: your-api-key" https://yourservice.dtz.rocks/profile
# Test with a bearer token
curl -H "Authorization: Bearer your-jwt-token" https://yourservice.dtz.rocks/profile
# Test unauthenticated (should return 401)
curl https://yourservice.dtz.rocks/profile
For more detailed information about DTZ authentication, see the Authentication documentation.