web-dev-qa-db-fra.com

Sessions dans l'authentification basée sur des jetons

Je crée une application en PHP Lumen qui retourne un jeton lors de la connexion. Je ne sais pas comment procéder au-delà.

Comment suis-je censé maintenir une session en utilisant ces jetons?

Plus précisément, comment puis-je stocker les jetons côté client si j'utilise reactjs ou Vanilla HTML/CSS/jQuery et les envoyer dans chaque demande que je fais pour la partie sécurisée de mon application web?

20
Flame of udun

Ce que je fais habituellement, c'est de conserver le jeton dans le stockage local, de cette façon, je peux conserver le jeton même si l'utilisateur quitte le site.

localStorage.setItem('app-token', theTokenFromServer);

Chaque fois que l'utilisateur charge la page, la première chose que je fais est de rechercher l'existence du jeton.

token = localStorage.getItem('app-token');

Si j'utilise react, je garderais le jeton sur l'état global (en utilisant redux par exemple):

function loadAppToken(token) {
  return {
    type: 'LOAD_TOKEN',
    payload: { token },
  };
}

Avec Vanilla javascript, je le garderais sur mon utilitaire de connexion. Ce qui pourrait ressembler à ceci:

const token = localStorage.getItem('app-token');

export function request(config) {
   const { url, ...others } = config;

   return fetch(url, {
     ...others,
     credentials: 'include',
     headers: {
       'Authorization': `Bearer ${token}`
     },
   });
}

J'aurais toujours un utilitaire de récupération dans une application React, similaire au code précédent, mais j'enverrais le jeton dans les options, en le plaçant dans un middleware redux pour chaque demande.

11
Crysfel

Supposons que vous souhaitiez créer une application avec.

  1. ReactJS
  2. API REST avec PHP
  3. Utilisation de JWT

1. Introduction

Vous devez oublier les sessions lors de la construction des API REST.

Les API REST sont censées être sans état, elles ne doivent donc pas dépendre des sessions, elles doivent traiter les demandes avec uniquement les données fournies par le client.

2. Authentification

Tout ce que le client veut faire, c'est seulement échanger des username et password contre un token.

Ceci est un exemple de requête HTTP

POST /api/v1/authentication HTTP/1.1
Host: localhost
Content-Type: application/json
{
    "username": "foo",
    "password": "bar"
}

Et la réponse est:

{
    "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

3. entrons dans plus de détails dans la demande/réponse

Comment notre API traitera-t-elle la demande d'authentification?

  1. Il vérifiera si un utilisateur avec un nom d'utilisateur foo et un mot de passe bar est fondé et est actif dans la base de données

  2. Il va générer un JWT (Json Web Token)

  3. Il renverra une réponse contenant le JWT

Il s'agit d'une méthode d'authentification super simple, par exemple.

public function authAction()
{
  /** Get your payload somehow */
  $request = $_POST;

  //Validate if username & password are given/

  $user = $this->model->auth($username, $password);

  if(!$user) {
    //throw error for not valid credentials
  }

  $jwt = $this->jwt->create($user);

  //return response with $jwt
}

Comme vous le voyez, aucune session n'est définie ou quoi que ce soit.

Comment notre côté client traitera-t-il la réponse?

Le client pourrait utiliser un package comme superagent pour gérer les demandes et les réponses à notre API de cette façon, le processus sera simplifié comme suit:

  let data = {
    username: email,
    password: password
  };

  request
    .post('/api/v1/authentication')
    .set('Content-Type', 'application/json')
    .send(data)
    .end(function (error, response) {
      //response.body.token
    });

4. Création de JWT côté serveur

Vous pouvez utiliser un package 3RD PT pour générer et valider JWT au lieu de l'écrire vous-même.

Regardez ceci package , vous pouvez voir comment cela se fait.

Et n'oubliez pas de toujours créer des signatures fortes. Je recommande d'utiliser RSA keys

Je ne fais pas de publicité ni ne soutiens ce projet, je l'ai trouvé utile de le partager ici. Je ne l'avais jamais utilisé, j'utilise quelque chose de similaire à cela sur mes projets NodeJS.

5. Enregistrer JWT côté client

Il y a deux façons comme vous le savez déjà localStorage & cookies Pour moi, j'utilise des cookies, car:

  1. Ils sont un peu plus sécurisés .
  2. La date d'expiration peut être définie sans implémenter une logique personnalisée.
  3. Support de navigateur plus ancien (très vieux navigateurs, donc ce n'est pas si important).

Mais tout dépend de vous.

6. Utilisation de JWT

Désormais, chaque demande adressée au serveur doit inclure votre JWT.

Dans votre REST API, vous devez écrire une méthode pour valider le JWT et l'échanger contre un objet utilisateur.

Exemple de demande:

  let jwt = ...; //GET IT FROM LOCALSTORAGE OR COOKIE

  request
    .get('/api/v1/posts')
    .set('Content-Type', 'application/json')
    .set('Authorization', jwt)
    .end(function (error, response) {

    });

Comment l'API traitera cette demande

public function postsAction()
{
  $jwt = $this->headers->get('Authorization');

  if(!$this->jwt->validate($jwt)) {
    //throw unauthorized error
  }

  $user = $this->model->exchangeJWT($jwt);

  //Your logic here
}

7. Date d'expiration et cookie

Si vous utilisez des cookies pour enregistrer votre JWT, soyez prudent avec la définition des dates d'expiration.

La date d'expiration du cookie doit être égale à la date d'expiration de JWT.

5
Codew

Travaille actuellement sur le même type d'application en utilisant Lumen pour l'API. En suivant 3 étapes pour l'authentification basée sur les jetons dans Lumen avec JWT :

1. Créer un jeton et revenir après la connexion réussie

public function login(Request $request) {
    $token = $this->jwt->attempt(['user_name' => $data['user_name'], 'password' => $data['password']]); //$token = $this->jwt->attempt($data); 
    if (!$token) {
        $response = array('success' => false, 'data' => null, 'detail' => array('message' => Messages::MSG_INVALID_USER, 'error' => array(Messages::MSG_INVALID_USER)));
        return response()->json($response);
    } else {
        $user = \Auth::setToken($token)->user();
        $data = array('token' => $token,'user_id' => $user->id);
        $response = array('success' => true, 'data' => $data, 'detail' => array('message' => Messages::MSG_SUCCESS, 'error' => null));
        return response()->json($response);
    }
}

2. Définir le middleware pour la vérification des jetons

public function handle($request, Closure $next, $guard = null) {
    try {
        $token = $request->header('X-TOKEN');
        $user_id = $request->header('X-USER');
        $user = \Auth::setToken($token)->user();
        if ($user && $user->id == $user_id) {
            return $next($request);
        } else {
            $response = array('success' => false, 'data' => null, 'detail' => array('message' => Messages::MSG_ERR_INVALID_TOKEN, 'error' => Messages::MSG_ERR_INVALID_TOKEN));
            return response()->json($response);
        }
    } catch (Exception $ex) {
        $response = array('success' => false, 'data' => null, 'detail' => array('message' => Messages::MSG_ERROR_500, 'error' => array($ex)));
        return response()->json($response);
    }
}

3. Stocker le jeton dans le stockage local ou dans les cookies

localStorage.setItem("Token", JSON.stringify(TokenData));
TokenData = JSON.parse(localStorage.getItem("Token"));

ou

$.cookie('Token', JSON.stringify(TokenData), {expires: 1, path: '/'});
TokenData = JSON.parse($.cookie("Token"));

4. Envoyer un jeton avec chaque demande dans les en-têtes

Demande avec en-têtes personnalisés

$.ajax({
    url: 'foo/bar',
    headers: { 'X-TOKEN': TokenData.Token ,'X-USER': TokenData.UserId}
});

En-têtes de chaque demande

$.ajaxSetup({
        headers: { 'X-TOKEN': TokenData.Token ,'X-USER': TokenData.UserId}
    });

J'espère que cela vous aidera.

Remarque: Ajoutez des vérifications et des validations de données lors de la lecture des données de localstorage ou cookies.

3
Govind Samrow

Vous pouvez le stocker dans le localStorage du navigateur, puis le définir dans l'en-tête pour chaque demande au serveur.

2
Giovanni Lobitos

Pour le chiffrement et le déchiffrement, vous pouvez utiliser le modèle de chiffrement de laravel intégré

tilisez Illuminate\Support\Facades\Crypt;

Ce que nous faisons pour générer un jeton d'API, c'est prendre un tableau de champs obligatoires.

Créons des données

$data = [
    'user_id' => $user->id,
    'time_stemp' => \Carbon::now() // Carbon is laravel's time model(class) for managing times
    'expire_on' => \Carbon::now()->addDays(2); //here i'm setting token expires time for 2 days you can change any
];

$data = serialize($data);

puis cryptez vos données avec Crypt

$accessToken = Crypt::encrypt($data);

Maintenant, envoyez à la partie frontale en réponse et enregistrez dans le stockage local ou dans un cookie tout ce qui n'est pas nécessaire ici vérifiera uniquement le serveur.

Maintenant, à chaque demande, passez ce jeton et côté serveur, créez un logiciel intermédiaire qui analysera vos données et si votre temps de jeton est inférieur, puis expirez, puis continuez sinon envoyez l'erreur 403 ou tout ce que vous voulez.

Comment analyser les données côté serveur

Créer un middleware en utilisant la commande: php artisan make: middleware ApiAuth est alors la partie de la poignée

//Accesstoken you passed in $headers or in $request param use whatever you like
$searilizerData = Crypt::decrypt($headers['AccessToken']);
$data = unserialize($searilizerData);
//check if expire_on is less then current server time
if($data['expire_on] <= \Curbon::now()){
   next(); // let them contuine and access data
} else {
      throw new Exception ("Your token has expired please regenerate your token",403);
}

J'espère que cela vous aidera :)

2
Binit Ghetiya

En fait, vous n'avez pas besoin de ReactJS ou VanillaJS. Juste du HTML pur et PHP en fait. Ce que je fais, c'est simplement le stocker comme cookie.

Tout d'abord, lorsque vous recevez le jeton de Lumen, enregistrez-le dans votre base de données utilisateur pour un utilisateur spécifique. Ensuite, définissez l'ID utilisateur et l'accès comme cookies qui expirent après un certain temps avec ce code:

setcookie('userid',$userid, time()+(3600 * 24 * 15),"/");
setcookie('accesstoken',$accesstoken, time()+(3600 * 24 * 15),"/");
header('Location: /home.php');
//You can change the 15 in setcookie() to amount of days the cookie will expire in.
//The "/" in setcookie is important, because it ensures the cookies will be available on every page the user visits on your website.
//The header function redirects to your home page after log in

Ensuite, voici à quoi ressemblerait votre page d'accueil. Il vérifie si le cookie accesstoken existe, s'il le fait, il vérifie à nouveau que le jeton correspond au jeton actuel dans la base de données utilisateur. S'il s'agit d'une correspondance, la page "connecté" s'affiche. Sinon, vous devez afficher/rediriger vers la page de connexion.

<?php
if (isset($_COOKIE['accesstoken']))
{
//connect to your user database and check that the cookie accesstoken matches
// if it doesn't match, deal with it appropriately, such as deleting all cookies then redirecting to login page.
}
?>
<!DOCTYPE HTML>
<html>
<head>
<title>Sup</title>
</head>
<body>
<?php if (isset($_COOKIE['accesstoken'])){ ?>

<h1>User logged in!</h1>
<h3>Do whatever you need to do if user is logged in</h3>

<?php } else { ?>

<h1>No accesstoken found</h1>
<h3>More than likely you will want to show login page here</h3>

<?php } ?>
</body>
</html>

puis se déconnecter est simple. Le code ci-dessous supprime les accesstokens en les définissant comme expirés:

setcookie("accesstoken", "", time() - 3600);
setcookie("userid", "", time() - 3600);
header('Location: /youareloggedout.html');

Rappelez-vous, c'est la BASE d'un système fonctionnel de connexion/déconnexion. Si j'expliquais toutes les mesures de sécurité nécessaires, ce message serait encore plus long. Assurez-vous de faire vos recherches. Certains sujets pour commencer sont les instructions préparées et la prévention des attaques XSS. :)

1
Rogi Solorzano

J'ai récemment terminé un portail Web React où nous avons utilisé JWT pour lancer, maintenir et expirer la session de l'utilisateur.

  1. Lors de la connexion, envoi des informations d'identification de l'utilisateur à l'API de connexion. En cas de succès, récupérez le jeton de l'API back-end. Le back-end conserve la génération et l'expiration des jetons.
  2. Stockez le jeton dans l'état de réaction (nous utilisons le magasin redux) et dans le stockage de session (au cas où la page est actualisée, nous pouvons le récupérer du stockage de session).
  3. (Facultatif) Démarrer un compteur par seconde dans le stockage de session (pour vérifier combien de temps l'utilisateur est inactif)
  4. Après la connexion, chaque appel API nécessite que le jeton soit envoyé dans l'en-tête. Les appels d'API sont effectués à l'aide de la récupération. Si l'appel d'API réussit, nous récupérons le jeton du back-end et nous le remplaçons par le jeton existant (restez à jour).
  5. Tous les appels d'API sont récupérés via une fonction générique customFetch. L'idée est d'avoir une extraction générique pour voir si la réponse principale est 401 (accès refusé). S'il s'agit de 401, le jeton a expiré ou n'est pas valide (l'utilisateur tente d'accéder à quelque chose sans connexion). Dans ce cas, nous jetons l'utilisateur hors du portail, de retour à la page de connexion/d'accueil (affichant l'erreur selon laquelle l'accès est refusé).
  6. (Facultatif) Si l'utilisateur est inactif trop longtemps (vérification du deuxième compteur> 900, c.-à-d. 15 min), nous montrons à l'utilisateur que la session est sur le point d'expirer, donne à l'utilisateur le choix de continuer. Si les clics des utilisateurs se poursuivent, nous appelons une API pour récupérer à nouveau le profil de l'utilisateur, garantissant ainsi que le jeton est toujours valide. Si l'API échoue, nous déconnectons l'utilisateur et le renvoyons à la page de connexion/page d'accueil. Le deuxième compteur revient à 1 juste avant tout appel d'API (l'utilisateur est actif et fait quelque chose).
  7. Inutile de dire qu'avant d'envoyer l'utilisateur à la page de connexion/page d'accueil par l'un des scénarios ci-dessus, nous effaçons le stockage de la session et réinitialisons l'état (magasin redux).
  8. En cas de rafraîchissement, nous récupérons le jeton du stockage de session et envoyons les actions initiales pour reconstruire l'état (magasin redux). Si l'une des actions (API) échoue, nous affichons le message à l'utilisateur que la session est expirée ou invalide et vous devez vous connecter, renvoyant ainsi l'utilisateur à la page de connexion/page d'accueil.

Extraits de code

Supposons que vous ayez récupéré le jeton depuis l'appel d'API de connexion:

définir le jeton dans le stockage et l'état de la session (magasin redux)

window.sessionStorage.setItem('partyToken', token)
store.dispatch({type: 'profile/setToken', payload: { token }})

jeton de récupération à partir du stockage ou de l'état de la session (magasin redux)

const token = window.sessionStorage.getItem('token')
const token = store.getState().profile && store.getState().profile.token

Bien sûr, vous pouvez définir une fonction commune où vous pouvez définir/actualiser le jeton après chaque appel d'API. Similaire pour la récupération, car vous avez besoin du jeton avant d'effectuer un appel API.

0
Umesh

Je vais écrire une tâche rapide et les meilleures pratiques, car il existe de nombreuses façons de le faire avec du code.

Backend

  • (POST) route de connexion {email, mot de passe} il créera un jeton. Vous pouvez utiliser JWT (Json Web Token) Le jeton sera retourné au client. À l'intérieur du jeton, vous pouvez stocker quelques détails de base: identifiant utilisateur, nom d'utilisateur, expiration du jeton, type d'utilisateur, etc. https://jwt.io/

Client

  • demande de connexion, passez {email, mot de passe}.

    En cas de succès, obtenez le jeton et stockez-le localement, le stockage local est préféré, mais le cookie est également possible.

  • à chaque chargement de page avec votre application React, vous devriez vérifier la fonction de ce jeton, il le déchiffrera et obtiendra les détails pour une utilisation ultérieure.

    Je veux dire obtenir le nom d'utilisateur, l'ID utilisateur, etc. Plus important si vous voulez l'ajouter, c'est "l'expiration", si le jeton a expiré, vous redirigez l'utilisateur vers la page de connexion, OR vous peut demander à nouveau un nouveau jeton, cela dépend vraiment de votre application.

  • la déconnexion, est assez simple ... il suffit de retirer le jeton du côté client et de le rediriger vers la page de connexion.

  • Assurez-vous que pour les pages "authentifiées", vous vérifiez que le jeton existe, et encore plus vous pouvez vérifier le type d'utilisateur.

** pour le décodage côté client de JWT, vous pouvez utiliser: https://www.npmjs.com/package/jwt-client

0
Tzook Bar Noy