web-dev-qa-db-fra.com

Comment un jeton d'accès utilisateur Facebook doit-il être utilisé côté serveur?

Préface

Je développe plusieurs services web et une poignée de clients (application web, mobile, etc.) qui s'interfaceront avec lesdits services via HTTP (s). Mon travail actuel consiste à concevoir une solution d'authentification et d'autorisation pour le produit. J'ai décidé de tirer parti des fournisseurs d'identité externes, tels que Facebook, Google, Microsoft, Twitter, etc. pour l'authentification.

J'essaie de résoudre le problème de "quand une demande arrive sur mon serveur, comment savoir qui est l'utilisateur et comment en être sûr?". Plus de questions ci-dessous également ...

Exigences

  1. Comptez sur des identités externes pour indiquer à qui j'ai affaire ('userId' est essentiellement tout ce qui m'importe).
  2. Le système doit utiliser une authentification basée sur des jetons (par opposition aux cookies par exemple ou à l'authentification de base).

    Je pense que c'est le bon choix pour évoluer sur plusieurs clients et serveurs tout en offrant un couplage lâche.

Workflow

Sur la base de ma lecture et de ma compréhension de l'authentification basée sur des jetons, voici comment j'imagine le flux de travail. Concentrons-nous pour l'instant sur Facebook dans un navigateur Web . Mon hypothèse est que d'autres fournisseurs d'identité externes devraient avoir des capacités similaires, même si je ne l'ai pas encore confirmé.

Remarque, au moment de la rédaction, je basais ce qui suit sur la version 2.2 de connexion Facebook

  1. Client: Initie la connexion à Facebook en utilisant JavaScript SDK
  2. Facebook: L'utilisateur authentifie et approuve les autorisations des applications (pour accéder au profil public de l'utilisateur par exemple)
  3. Facebook: Envoie une réponse au client qui contient le jeton d'accès, l'ID et la demande signée de l'utilisateur
  4. Client: Stocke le jeton d'accès utilisateur dans la session du navigateur ( géré par le SDK de manière pratique )
  5. Client: Fait une demande à mon service Web pour une ressource sécurisée en envoyant le jeton d'accès de l'utilisateur dans l'en-tête d'autorisation + l'ID de l'utilisateur (dans l'en-tête personnalisé) potentiellement)
  6. Serveur: Lit le jeton d'accès utilisateur depuis l'en-tête de la demande et lance la vérification en envoyant une demande à l'API graphique debug_token fournie par Facebook
  7. Facebook: Répond au serveur avec les informations de jeton d'accès utilisateur (contient appId et userId)
  8. Serveur: Termine la vérification du jeton en comparant l'appId à ce qui est attendu (connu de lui-même) et l'ID utilisateur à ce qui a été envoyé à la demande du client
  9. Serveur: Répond au client avec la ressource demandée (en supposant le chemin d'autorisation heureux)

J'imagine que les étapes 5 à 9 seraient répétées pour les demandes ultérieures au serveur (alors que le jeton d'accès de l'utilisateur est valide - non expiré, révoqué du côté FB, les autorisations d'application ont été modifiées, etc.)

Voici un diagramme pour vous aider à suivre les étapes. Veuillez comprendre que ce système n'est pas une application d'une seule page (SPA). Les services Web mentionnés sont des points de terminaison API servant essentiellement à renvoyer des données JSON aux clients; ils ne servent pas HTML/JS/CSS (à l'exception des serveurs clients Web).

Workflow diagram

Questions

  1. Tout d'abord, y a-t-il des lacunes/chutes flagrantes avec l'approche décrite en fonction de ma préface et de mes exigences?

  2. Est-ce qu'une requête sortante est envoyée à Facebook pour vérifier le jeton d'accès (étapes 6-8 ci-dessus) par requête client requise/recommandée?

    Je sais à tout le moins, je dois vérifier le jeton d'accès provenant de la demande du client. Cependant, l'approche recommandée pour les vérifications ultérieures après la première m'est inconnue. S'il existe des modèles typiques, je suis intéressé à en entendre parler. Je comprends qu'ils peuvent dépendre de l'application en fonction de mes besoins; cependant, je ne sais pas encore quoi chercher. Je ferai preuve de diligence raisonnable une fois que j'aurai une idée de base.

    Par exemple, des pensées possibles:

    • Hachez la paire jeton d'accès + userId une fois la première vérification terminée et stockez-la dans un cache distribué (accessible par tous les serveurs Web) avec une expiration égale aux jetons d'accès. À la demande ultérieure des clients, hachez la paire jeton d'accès + ID utilisateur et vérifiez son existence dans le cache. S'il est présent, la demande est autorisée. Sinon, contactez l'API graphique Facebook pour confirmer le jeton d'accès. Je suppose que cette stratégie pourrait être réalisable si j'utilise HTTPS (ce que je serai). Cependant, comment les performances se comparent-elles?

    • La réponse acceptée dans cette question StackOverflow recommande de créer un jeton d'accès personnalisé une fois la première vérification du jeton d'utilisateur Facebook terminée. Le jeton personnalisé serait ensuite envoyé au client pour les demandes suivantes. Je me demande cependant si cela est plus complexe que la solution ci-dessus. Cela nécessiterait la mise en œuvre de mon propre fournisseur d'identité (quelque chose que je veux éviter parce que je veux utiliser des fournisseurs d'identité externes en premier lieu…). Y a-t-il un mérite à cette suggestion?

  3. Le champ signedRequest est-il présent sur la réponse à l'étape 3 ci-dessus (mentionné ici ), équivalent au paramètre de demande signée ici dans le flux ‘games canvas login’?

    Ils semblent être suggérés comme équivalents puisque le premier est lié au second dans la documentation. Cependant, je suis surpris que la stratégie de vérification mentionnée sur la page des jeux ne soit pas mentionnée dans la "création manuelle d'un flux de connexion" page de la documentation Web.

  4. Si la réponse à la question n ° 3 est "Oui", la même stratégie de confirmation d’identité peut-elle décoder la signature et la comparer à ce qui devrait être utilisé côté serveur?

    Decode & compare from FB docs

    Je me demande si cela peut être exploité au lieu de faire un appel sortant à l'API graphique debug_token (étape n ° 6 ci-dessus) pour confirmer le jeton d'accès comme recommandé ici :

    debug_token graph API from FB docs

    Bien sûr, afin de faire la comparaison côté serveur, la partie de demande signée devrait être envoyée avec la demande au serveur (étape n ° 5 ci-dessus). En plus de la faisabilité sans sacrifier la sécurité, je me demande comment les performances se compareraient à l’émission de l’appel sortant.

  5. Pendant que j'y suis, dans quel scénario/dans quel but, persisteriez-vous le jeton d'accès d'un utilisateur à une base de données par exemple? Je ne vois pas de scénario où j'aurais besoin de faire cela, cependant, je peux ignorer quelque chose. Je suis curieux de savoir que certains scénarios courants pourraient être de spark quelques réflexions.

Merci!

64
Scott Lin

D'après ce que vous décrivez, je suggère d'utiliser un flux de connexion côté serveur comme décrit dans

de sorte que le jeton se trouve déjà sur votre serveur et n'a pas besoin d'être transmis par le client. Si vous utilisez des connexions non cryptées, cela pourrait constituer un risque pour la sécurité (par exemple, pour les attaques de l'homme du milieu).

Les étapes seraient les suivantes:

(1) Connexion des gens

Vous devez spécifier l'autorisation que vous souhaitez collecter auprès des utilisateurs dans le paramètre scope. La demande peut être déclenchée uniquement via un lien normal:

GET https://www.facebook.com/dialog/oauth?
    client_id={app-id}
   &redirect_uri={redirect-uri}
   &response_type=code
   &scope={permission_list}

Voir

(2) Confirmez l'identité

GET https://graph.facebook.com/oauth/access_token?
    client_id={app-id}
   &redirect_uri={redirect-uri}
   &client_secret={app-secret}
   &code={code-parameter}

(3) Inspectez le jeton d'accès

Vous pouvez inspecter le jeton comme vous l'avez déjà dit dans votre question via

GET /debug_token?input_token={token-to-inspect}
    &access_token={app-token-or-admin-token}

Cela ne devrait être fait que côté serveur, car sinon vous rendriez votre jeton d'accès à l'application visible pour les utilisateurs finaux (ce n'est pas une bonne idée!).

Voir

(4) Extension du jeton d'accès

Une fois que vous avez obtenu le jeton (de courte durée), vous pouvez effectuer un appel pour étendre le jeton comme décrit dans

comme suit:

GET /oauth/access_token?grant_type=fb_exchange_token
    &client_id={app-id}
    &client_secret={app-secret}
    &fb_exchange_token={short-lived-token}

(5) Stockage des jetons d'accès

Concernant le stockage des tokens sur le serveur, FB propose de le faire:

(6) Gestion des jetons d'accès expirés

Étant donné que FB ne vous avertit pas si un jeton a expiré (et si vous n'enregistrez pas la date d'expiration et ne la comparez pas à l'horodatage actuel avant de passer un appel), il est possible que vous receviez des messages d'erreur de FB si le jeton est invalide. (après max.60 jours). Le code d'erreur sera 190:

{
  "error": {
    "message": "Error validating access token: Session has expired at unix 
                time SOME_TIME. The current unix time is SOME_TIME.", 
    "type": "OAuthException", 
    "code": 190
  }
}

Voir

Si le jeton d'accès devient invalide, la solution consiste à demander à la personne de se reconnecter, auquel cas vous pourrez à nouveau effectuer des appels API en son nom. Le flux de connexion utilisé par votre application pour les nouvelles personnes devrait déterminer la méthode à adopter.

23
Tobi
  1. Je ne vois aucune lacune ou fosse flagrante, mais je ne suis pas un expert en sécurité.
  2. Une fois que votre serveur a vérifié le jeton donné (étape 8), comme vous l'avez dit:

La réponse acceptée dans cette question StackOverflow recommande de créer un jeton d'accès personnalisé une fois la première vérification du jeton d'utilisateur Facebook terminée. Le jeton personnalisé serait ensuite envoyé au client pour les demandes suivantes. Je me demande cependant si cela est plus complexe que la solution ci-dessus. Cela nécessiterait la mise en œuvre de mon propre fournisseur d'identité (quelque chose que je veux éviter parce que je veux utiliser des fournisseurs d'identité externes en premier lieu…). Y a-t-il un mérite à cette suggestion?

À mon humble avis, c'est la voie à suivre. J'utiliserais https://jwt.io/ qui vous permet d'encoder des valeurs (le userId par exemple) en utilisant une clé secrète. Ensuite, votre client attache ce jeton à chaque demande. Ainsi, vous pouvez vérifier la demande sans avoir besoin d'un tiers (vous n'avez pas non plus besoin de requêtes de base de données). La bonne chose ici est qu'il n'est pas nécessaire de stocker le jeton sur votre base de données.

Vous pouvez définir une date d'expiration sur le jeton, pour forcer le client à s'authentifier à nouveau avec le tiers quand vous le souhaitez.

  1. Supposons que vous souhaitiez que votre serveur puisse effectuer certaines actions sans interaction avec le client. Par exemple: Open graph stories . Dans ce scénario, car vous devez publier quelque chose au nom de l'utilisateur, vous aurez besoin du jeton d'accès stocké sur votre base de données.

(Je ne peux pas vous aider avec les 3 et 4 questions, désolé).

1
ilopezluna