Authentification des services

Authentification des services déployés

DownToZero (DTZ) fournit une authentification intégrée pour vos services déployés, ce qui facilite la création d’applications sécurisées sans gérer votre propre infrastructure d’authentification.

Vue d’ensemble

Lorsque vous déployez un service sur DTZ, la plateforme fournit automatiquement des informations d’identification et des informations de contexte via des variables d’environnement. Votre service peut les utiliser pour :

  • Authentifier des requêtes vers d’autres services DTZ (appels API, stockage d’objets, etc.)
  • Vérifier les requêtes entrantes des utilisateurs
  • Accéder à des ressources spécifiques au contexte
  • Mettre en œuvre une communication sécurisée entre services

Variables d’environnement

DTZ injecte automatiquement les variables d’environnement suivantes dans vos conteneurs de service :

Variable Description Exemple
DTZ_ACCESS_TOKEN Un token JWT pour accéder aux services DTZ dans votre contexte. eyJhbGciOiJSUzI1NiI...
DTZ_CONTEXT_ID L’identifiant de votre contexte DTZ. context-3cd84429-64a4-4226-b868-c83feeff0f46
PORT Le port sur lequel votre service doit écouter. 80

Authentification des requêtes entrantes

Votre service déployé peut authentifier les requêtes entrantes en utilisant plusieurs méthodes :

Authentification par clé API

Les utilisateurs peuvent s’authentifier auprès de votre service en fournissant la clé dans l’en-tête X-API-KEY.

curl -H "X-API-KEY: your-api-key" https://yourservice.dtz.rocks/api/endpoint

Authentification par jeton Bearer

Fournissez un token JWT dans l’en-tête Authorization pour vous authentifier.

curl -H "Authorization: Bearer your-jwt-token" https://yourservice.dtz.rocks/api/endpoint

Authentification basique

Vous pouvez également passer des clés API en utilisant l’authentification basique.

curl -u apikey:your-api-key https://yourservice.dtz.rocks/api/endpoint

Authentification basée sur les cookies

Pour les applications web, le service d’identité DTZ peut gérer l’authentification via des cookies du navigateur.

Flux OAuth

DTZ fournit une authentification OAuth automatique pour les applications web. Lorsqu’un utilisateur non authentifié accède à votre service, il est automatiquement redirigé vers la page de connexion DTZ, puis redirigé de retour après une connexion réussie.

Utiliser l’authentification DTZ dans votre service

Effectuer des requêtes authentifiées vers les services DTZ

Utilisez la variable d’environnement DTZ_ACCESS_TOKEN pour effectuer des appels authentifiés vers d’autres services DTZ.

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

Vérification de l’authentification entrante

DTZ gère automatiquement l’authentification des requêtes entrantes. Lorsqu’un utilisateur effectue une requête authentifiée vers votre service, DTZ :

  1. Valide les informations d’authentification.
  2. Convertit les clés API en tokens JWT.
  3. Transfère la requête avec un en-tête Authorization: Bearer <token>.

Votre service reçoit le token JWT et peut en extraire les informations utilisateur.

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' });
    }
});

Structure des tokens JWT

Les tokens JWT DTZ contiennent les revendications suivantes :

Claim Description Exemple
iss Émetteur (toujours “dtz.rocks”) "dtz.rocks"
sub Sujet (ID d’identité de l’utilisateur) "identity-abc123..."
aud Audience (toujours “dtz.rocks”) "dtz.rocks"
scope ID du contexte "context-3cd84429..."
roles Rôles/permissions de l’utilisateur ["https://dtz.rocks/context/admin/{context_id}"]
contexts Contextes disponibles ["context-3cd84429..."]
exp Date d’expiration 1640995200
iat Date d’émission 1640908800

Contrôle d’accès basé sur les rôles

DTZ utilise un contrôle d’accès basé sur les rôles avec des identifiants de rôle basés sur des URI. Les motifs de rôle courants incluent :

  • https://dtz.rocks/context/admin/{context_id} - Administrateur de contexte
  • https://dtz.rocks/containers/admin/{context_id} - Administrateur du service de conteneurs
  • https://dtz.rocks/objectstore/admin/{context_id} - Administrateur du stockage d’objets

Vous pouvez vérifier les rôles dans votre 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

Bonnes pratiques

Sécurité

  • Validez toujours que les tokens JWT contiennent les revendications attendues.
  • Vérifiez les rôles des utilisateurs avant d’accorder l’accès aux opérations sensibles.
  • Utilisez HTTPS pour toutes les communications.
  • Ne consignez pas les tokens d’authentification sensibles.

Gestion des erreurs

  • Renvoyez les codes d’état HTTP appropriés (par ex., 401 pour non autorisé, 403 pour interdit).
  • Fournissez des messages d’erreur significatifs sans divulguer d’informations sensibles.

Performance

  • Mettez en cache les résultats de validation des tokens JWT lorsque cela est possible.
  • Utilisez le pooling de connexions pour les appels API DTZ.
  • Envisagez d’implémenter une limitation de débit des requêtes.

Exemple : service authentifié complet

Voici un exemple complet d’un service Python Flask avec l’authentification DTZ :

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)

Dépannage

Problèmes courants

  • Jeton d’authentification introuvable : Assurez-vous que votre service est déployé via le service de conteneurs DTZ et que les variables d’environnement sont correctement lues.
  • Erreurs de token invalide : Vérifiez que vous extrayez correctement le token depuis l’en-tête Authorization et que l’analyse du JWT fonctionne.
  • Erreurs 403 Forbidden : Vérifiez que l’utilisateur dispose des rôles requis dans son token JWT.
  • Échec de l’authentification service-à-service : Assurez-vous d’utiliser la variable d’environnement DTZ_ACCESS_TOKEN pour les requêtes sortantes.

Tester l’authentification

Vous pouvez tester l’authentification de votre service avec 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

Pour plus d’informations détaillées sur l’authentification DTZ, consultez la documentation d’authentification.