web-dev-qa-db-fra.com

Créez une API oauth sécurisée avec passport.js et express.js (node.js)

J'ai un problème spécifique, à moins que je ne fasse pas les choses correctement.

J'ai une application avec 2 côtés, un client (site html) et une API (construite avec express + mongodb). L'API devra être accessible en toute sécurité. Ils sont tous deux sur des domaines différents, pour l'instant disons que mon site est sur domain.com et que mon API est sur api.com:3000.

J'ai ajouté un passeport pour obtenir un jeton d'accès auprès de Github au fur et à mesure que je crée mes utilisateurs avec leurs données, afin que je puisse avoir un "Sign in with Github".

Mon processus actuel est:

1) le client (le site), ouvre une fenêtre contextuelle sur l'API

window.open("http://api.com:3000/oauth")

2) Le serveur express, démarrez le processus de passeport:

app.get('/oauth', passport.authenticate('github'), function(req, res) {

});

Pour la stratégie, j'ai utilisé ce code .

3) Je redirige le rappel vers une URL qui ferme la fenêtre contextuelle (javascript avec window.close ()):

app.get('/oauth/callback', passport.authenticate('github', { failureRedirect: '/' }), function(req, res) {
    res.redirect('/auth_close');
});

À ce stade, tout va bien, je suis maintenant connecté à l'API. Mon problème est de savoir comment récupérer les données sur le client, car le client ne sait pas encore quoi que ce soit sur accessToken, utilisateur ou identifiant utilisateur.

Donc, du client (le site qui ouvre la popup), j'ai essayé différentes approches:

  • obtenir une valeur dans le popup: ne fonctionne pas à cause de la redirection, je perds la trace des informations javascript du popup

  • appel de mon API pour obtenir l '"utilisateur actuel" en session, tel que http://api.com:3000/current Une demande supplémentaire, pas idéale, mais celle-ci fonctionne. Cependant, j'ai toujours un problème.

Cette URL/adresse actuelle renvoie l'utilisateur si j'y parviens depuis le navigateur, car le navigateur envoie dans la requête d'en-tête le cookie de session express:

Cookie:connect.sid=s%3AmDrM5MRA34UuNZqiL%2BV7TMv6.Paw6O%2BtnxQRo0vYNKrher026CIAnNsHn4OJdptb8KqE

Le problème est que je dois faire cette demande à partir de jquery ou similaire, et c'est là que ça échoue car la session n'est pas envoyée. Donc, l'utilisateur n'est pas renvoyé:

app.get('/current', function() {
    // PROBLEM HERE, req.user is undefined with ajax calls because of the session!
});

J'ai trouvé un moyen de le faire fonctionner mais je ne suis pas content parce que j'aurai des problèmes de navigation CORS avec plusieurs navigateurs, cela ajoute à exprimer:

res.header("Access-Control-Allow-Credentials", "true");

Et ajoutez le champ withCredentials dans un appel jquery ajax, comme expliqué ici .

Une mappe de paires fieldName-fieldValue à définir sur l'objet XHR natif. Par exemple, vous pouvez l'utiliser pour définir withCredentials sur true pour les demandes interdomaines si nécessaire.

$.ajax({
   url: a_cross_domain_url,
   xhrFields: {
      withCredentials: true
   }
});

Je ne suis pas non plus satisfait de cela car je perds le caractère générique de mon en-tête: Access-Control-Allow-Origin, je dois spécifier un domaine, comme expliqué ici .

Donc, je ne suis pas sûr de l’approche que je devrais adopter ici, la seule chose dont j’ai besoin est que le client obtienne un accessToken ou un ID d’utilisateur du processus de passeport. L'idée est d'utiliser ce jeton dans chaque appel de l'API pour valider les appels.

Un indice?

17
Soundstep

J'ai eu le même problème. J'utilisais la stratégie locale sur la page de connexion, puis je vérifiais si l'utilisateur était sur la session pour d'autres demandes.

Comme vous le dites, la solution consiste à utiliser CORS pour que l'ID de session soit transmis dans un cookie à l'aide de XMLHTTPRequest.

Au lieu d'utiliser le CORS qui ne fonctionne pas encore sur tous les navigateurs, j'ai décidé d'utiliser des jetons d'accès pour d'autres requêtes. Le flux de travail que j'ai utilisé est le suivant:

POST /login
  • Le nom d'utilisateur et le mot de passe sont transmis dans le corps.
  • Authentification à l'aide de la stratégie locale.
  • La réponse renvoie l'objet utilisateur, y compris access_token

GET/endpoint/abc123? Access_token = abcdefg

  • Jeton obtenu à partir de la réponse de connexion
  • Authentification à l'aide de la stratégie au porteur (dans passport-http-bearer)

Les sessions ne sont plus nécessaires dans Express.

J'espère que cette alternative aide.

13
Scott Switzer

Avant de configurer quoi que ce soit dans Express app, utilisez les éléments suivants (exactement les mêmes) pour définir l'en-tête de la réponse pour plusieurs domaines:

app.use(function(req, res, next) {
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Origin', req.headers.Origin);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
if ('OPTIONS' == req.method) {
     res.send(200);
 } else {
     next();
 }
});
1
sam100rav