Aller au contenu

Auth pré-applicative sur ALB — Cognito et OIDC au load balancer

ALB peut authentifier la requête avant qu'elle n'atteigne votre cible. Pools d'utilisateurs Cognito, OIDC Okta ou Auth0, JWT transmis à l'app. Le pattern vous évite de bricoler votre propre passerelle d'auth pour les outils internes et les consoles d'admin.

Une console d’admin interne pour l’équipe plateforme commence comme une app Flask derrière un ALB, avec un seul identifiant codé en dur dans une variable d’environnement. Trois mois plus tard il y a 12 personnes dans l’équipe, dont trois sont parties et une qui a changé de rôle. L’identifiant n’a pas changé. Le chemin de moindre résistance vers une « vraie auth » ressemble à construire une page de connexion en Flask, l’intégrer au tenant Okta de l’entreprise, gérer les jetons, manipuler le refresh. Une semaine de travail pour un outil interne. Le chemin plus court est l’authentification ALB — l’ALB fait la danse Okta, valide l’utilisateur, et transfère la requête à l’app Flask avec un JWT signé dans un en-tête. L’app Flask fait confiance à l’en-tête et lit l’utilisateur dans le JWT. Pas de page de connexion, pas de gestion de jeton, pas de logique de refresh. Le pattern fonctionne pour les pools d’utilisateurs Cognito (natif AWS), les fournisseurs OIDC (Okta, Auth0, Google Workspace, Azure AD), et est le bon défaut pour les outils internes, les consoles d’admin, et toute application où vous chercheriez sinon à mettre un reverse proxy avec oauth2-proxy devant.

Ce qui se passe

ALB supporte deux actions d’authentification sur une règle de listener :

  • authenticate-cognito — ALB redirige les requêtes non authentifiées vers l’UI hébergée d’un pool d’utilisateurs Cognito, complète le flux OAuth, définit un cookie de session (AWSELBAuthSessionCookie), et transfère la requête à la cible avec un JWT signé dans l’en-tête X-Amzn-Oidc-Data. La cible lit l’en-tête pour identifier l’utilisateur.

  • authenticate-oidc — Même flux, mais contre un fournisseur d’identité conforme OIDC (Okta, Auth0, Google Workspace, Azure AD, Keycloak, etc.). Vous configurez l’URL de l’issuer, l’ID client et le secret, l’endpoint d’autorisation, l’endpoint de jeton, et l’endpoint d’info utilisateur. ALB gère la redirection, l’échange de code, la validation du jeton, et le cookie de session.

Le flux :

  1. Le client demande /admin/dashboard.
  2. ALB vérifie le cookie de session. Aucun présent.
  3. ALB redirige le client vers l’endpoint d’autorisation du fournisseur d’identité (par exemple https://exemple.okta.com/oauth2/v1/authorize) avec client_id, redirect_uri, response_type=code, scope.
  4. L’utilisateur s’authentifie au fournisseur (MFA, etc.).
  5. Le fournisseur redirige vers l’endpoint /oauth2/idpresponse d’ALB avec un code d’autorisation.
  6. ALB échange le code contre des jetons à l’endpoint de jeton du fournisseur.
  7. ALB valide le jeton d’ID, récupère les infos utilisateur, définit le cookie de session, et redirige le client vers l’URL d’origine.
  8. Maintenant le client a le cookie. ALB transfère la requête à la cible, avec X-Amzn-Oidc-Data (le JWT signé contenant les claims utilisateur), X-Amzn-Oidc-AccessToken, et les en-têtes X-Amzn-Oidc-Identity.

La cible fait confiance aux en-têtes parce qu’ils ont été définis par ALB après le flux d’auth. Le JWT dans X-Amzn-Oidc-Data est signé avec une clé gérée par AWS ; la cible peut vérifier la signature contre l’endpoint JWKS à https://public-keys.auth.elb.<region>.amazonaws.com/<kid>. Pour les outils internes où ALB est la seule entrée et que le groupe de sécurité l’impose, la vérification de signature peut être sautée et l’en-tête directement fait confiance.

Ce que cela vous épargne :

  • Pas d’UI de connexion dans votre application. ALB redirige vers le fournisseur ; le fournisseur gère l’UI.
  • Pas de stockage de jeton dans l’application. Le cookie de session est opaque pour la cible ; le JWT dans l’en-tête est en lecture seule.
  • Pas de logique de refresh. ALB rafraîchit le jeton quand le cookie de session est renouvelé.
  • Auth par règle. Un seul ALB peut héberger un /admin authentifié et un /healthz non authentifié parce que chacun est une règle de listener séparée avec ses propres actions.

Ce que cela ne règle pas :

  • L’autorisation. L’authentification ALB confirme qui est l’utilisateur. Elle ne dit pas ce qu’il peut faire. L’application doit quand même lire les groupes ou rôles de l’utilisateur dans le JWT et décider si l’action est permise. ALB a un paramètre « on-unauthenticated-request » mais il contrôle le comportement de redirection-vs-refus, pas le contrôle d’accès fin.

  • L’auth service à service. L’authentification ALB est pour les flux face humain. Un client machine ne peut pas compléter le flux de code OAuth. Pour service à service, utilisez mTLS, des requêtes signées, ou un chemin de clé API séparé.

  • L’état SSO cross-région. Le cookie de session est par ALB. Un utilisateur authentifié contre un ALB n’est pas automatiquement authentifié contre un autre dans une région différente. Pour SSO à travers plusieurs ALB, configurez le même client OIDC et le fournisseur gère la session.

Comment vérifier

Configurer une règle de listener pour l’auth Cognito :

aws elbv2 modify-listener \
  --listener-arn "$LISTENER_ARN" \
  --default-actions '[
    {
      "Type": "authenticate-cognito",
      "AuthenticateCognitoConfig": {
        "UserPoolArn": "arn:aws:cognito-idp:us-east-1:123456789012:userpool/us-east-1_abc",
        "UserPoolClientId": "1a2b3c4d5e6f",
        "UserPoolDomain": "admin-tools-auth"
      },
      "Order": 1
    },
    {
      "Type": "forward",
      "TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:...",
      "Order": 2
    }
  ]'

Les deux actions sont séquentielles — Cognito d’abord, puis forward. ALB ne transférera pas à la cible tant que l’action Cognito n’est pas complétée.

Configurer une règle OIDC pour Okta :

aws elbv2 create-rule \
  --listener-arn "$LISTENER_ARN" \
  --priority 10 \
  --conditions '[{"Field":"path-pattern","Values":["/admin/*"]}]' \
  --actions '[
    {
      "Type": "authenticate-oidc",
      "AuthenticateOidcConfig": {
        "Issuer": "https://exemple.okta.com",
        "AuthorizationEndpoint": "https://exemple.okta.com/oauth2/v1/authorize",
        "TokenEndpoint": "https://exemple.okta.com/oauth2/v1/token",
        "UserInfoEndpoint": "https://exemple.okta.com/oauth2/v1/userinfo",
        "ClientId": "0oa1b2c3d4e5f6g7h8i9",
        "ClientSecret": "<secret>",
        "Scope": "openid profile email groups",
        "OnUnauthenticatedRequest": "authenticate"
      },
      "Order": 1
    },
    {
      "Type": "forward",
      "TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:...",
      "Order": 2
    }
  ]'

Le Scope détermine quels claims arrivent dans le JWT. openid profile email groups est la combinaison standard pour « dites-moi qui ils sont et à quels groupes ils appartiennent ».

La configuration d’application Okta côté fournisseur doit inclure :

  • URI de redirection : https://<votre-domaine-alb>/oauth2/idpresponse — c’est fixé par ALB.
  • Type d’octroi : Code d’autorisation.
  • Scopes : Ce que vous listez dans le paramètre Scope de la règle ALB.

Vérifier que la cible reçoit les en-têtes après que le flux est complété. Un endpoint de test qui imprime les en-têtes :

# gestionnaire Flask minimal
@app.route('/whoami')
def whoami():
    import json, base64
    oidc_data = request.headers.get('X-Amzn-Oidc-Data', '')
    # le JWT est encodé en base64 ; séparer sur les points et décoder le payload
    parts = oidc_data.split('.')
    if len(parts) == 3:
        payload = base64.urlsafe_b64decode(parts[1] + '==')
        return json.dumps(json.loads(payload), indent=2)
    return 'pas de données oidc'

Après authentification, la réponse contient les claims du JWT — email, groups, sub, iss, exp. L’application lit ceux-ci directement.

Confirmer que le cookie de session est défini :

curl -v -c cookies.txt -L https://admin.exemple.com/
# suivre les redirections à travers le fournisseur, puis vérifier le jar de cookies
grep AWSELBAuthSessionCookie cookies.txt

Le cookie a un path /, un domaine sur le domaine ALB, HTTP-only, secure.

La correction

Le runbook de déploiement pour ajouter l’auth ALB à un outil interne existant :

  1. Choisir le fournisseur. Cognito si l’annuaire utilisateur est natif AWS ou si vous n’avez pas de fournisseur existant. OIDC contre le fournisseur de l’entreprise (Okta, Azure AD, Google Workspace) si l’annuaire vit déjà là.

  2. Créer l’application au fournisseur. URL d’issuer, ID client et secret, URI de redirection définie à https://<domaine-alb>/oauth2/idpresponse. Scopes définis à openid profile email plus le claim de groupe nécessaire pour l’autorisation.

  3. Décider de la portée par règle. Un seul ALB avec /admin/* authentifié et /api/health non authentifié permet à la vérification de santé de fonctionner sans authentification. L’action par défaut couvre les chemins non spécifiés.

  4. Configurer les règles de listener avec les actions authenticate et forward dans l’ordre. L’action d’auth est Order: 1, le forward est Order: 2.

  5. Mettre à jour l’application pour lire l’en-tête JWT. Ajoutez un middleware qui parse X-Amzn-Oidc-Data, extrait le courriel et les groupes, et les attache à la requête. L’application est maintenant consciente de l’identité sans posséder le flux d’auth.

  6. Tester le chemin non-sain. L’URI de redirection de l’application au fournisseur doit correspondre exactement. Une différence de slash final échoue silencieusement avec une erreur générique. Vérifiez avec les journaux du fournisseur.

  7. Décider de la durée de session. Le cookie de session ALB défaut à 7 jours. Pour une console d’admin, 8 heures est plus approprié. Définir SessionTimeout sur l’action d’auth.

Trois règles que nous tenons :

Vérifier la signature JWT dans les apps de niveau production. Pour les outils internes où ALB est la seule entrée et que le groupe de sécurité l’impose, faire confiance à l’en-tête est acceptable. Pour tout ce qui pourrait être atteint par une autre route, récupérez le JWKS et vérifiez la signature. AWS publie les clés à l’endpoint documenté.

Sauter l’auth ALB pour service à service. Les clients machines ne peuvent pas compléter le flux de redirection. Utilisez une règle de listener séparée pour /api/* avec un schéma d’authentification approprié aux machines (clé API, mTLS, requêtes signées).

L’action d’auth s’exécute avant toute autre action sur la règle. Si vous voulez aussi journaliser les requêtes non authentifiées, instrumentez le fournisseur, pas la cible. La cible ne voit que les requêtes déjà authentifiées.

Pour plus de détails sur la mécanique des règles de listener sur laquelle cela s’appuie, voir Règles de listener ALB — routage par hôte et par chemin. Pour les vérifications de santé qui doivent rester non authentifiées, voir Tuning de vérifications de santé pour cibles SaaS.


Stack Harbor conçoit les topologies ALB + NLB et le modèle d’opération autour dans le cadre de l’onboarding Gestion AWS et des Environnements infogérés en continu. Si vos outils internes partagent encore un identifiant codé en dur, commencez par une consultation d’infrastructure.