Search code examples
flutterdartsymfonyjwtaccess-token

Retrieve data from Jwt Token - Flutter and Symfony


I'm developing a Flutter application connected to a Symfony API. After authentication, I need to retrieve the data of the user connected to the app from the token.

Problem: I can retrieve the roles of a user, which is an attribute (array of strings) of the User class. However, I can't do that for other attributes. I tested if my token contains the keys, and it shows that it doesn't contain any key other than "roles."

I'll share the endpoint symfony, my user class, the login from flutter, and how i retrieve the roles from token

endPoint Symfony :

class AuthController extends AbstractController
{
    private $doctrine;
    private $jwtManager;

    
    public function __construct(ManagerRegistry $doctrine, JWTTokenManagerInterface $jwtManager) 
    {
        $this->doctrine = $doctrine;
        $this->jwtManager = $jwtManager;

    }

    /**
     * @Route("/api/login_check", name="app_login_check", methods={"POST"})
     */
    public function loginCheck(Request $request, UserPasswordHasherInterface $passwordEncoder): JsonResponse
    {
        $data = json_decode($request->getContent(), true);

        $cin = $data['cin'] ?? '';
        $password = $data['password'] ?? '';

        $user = $this->doctrine->getRepository(Utilisateur::class)->findOneBy(['cin' => $cin]);

        if (!$user || !$passwordEncoder->isPasswordValid($user, $password)) {
            throw new BadCredentialsException('Invalid cin or password');
        }

        $token = $this->jwtManager->create($user);

        return new JsonResponse(['token' => $token]);
    }

}

Class User :

<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use App\Repository\UtilisateurRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity(repositoryClass: UtilisateurRepository::class)]
#[UniqueEntity(fields: ['cin'], message: 'There is already an account with this cin')]
#[Vich\Uploadable]
#[ApiResource(
    operations: [
        new Get()
    ]
)]
class Utilisateur implements UserInterface, PasswordAuthenticatedUserInterface
{


    #[ORM\Id]
    #[ORM\Column(length: 180, unique: true)]
    private ?string $cin = null;

    #[ORM\Column]
    private array $roles = [];


    #[ORM\OneToMany(mappedBy: 'utilisateur', targetEntity: Commentaire::class, cascade: ["remove"])]
    private Collection $commentaires;


    #[ORM\Column(length: 255)]
    private ?string $password = null;

    #[ORM\Column(length: 255, nullable: true)]
    private ?string $email = null;

    #[ORM\Column(length: 255, nullable: true)]
    private ?string $nom = null;

    #[ORM\Column(length: 255, nullable: true)]
    private ?string $prenom = null;

    #[ORM\Column(length: 10, nullable: true)]
    private ?string $genre = null;

    #[ORM\Column(length: 50, nullable: true)]
    private ?string $telephone = null;

    #[ORM\ManyToOne]
    private ?Profil $profil = null;

    #[ORM\ManyToOne(inversedBy: 'utilisateurs')]
    private ?Departement $departement = null;

    #[ORM\ManyToOne(inversedBy: 'utilisateurs')]
    private ?Pole $pole = null;

    #[ORM\ManyToOne(inversedBy: 'utilisateurs')]
    private ?Domaine $domaine = null;

    #[ORM\OneToMany(mappedBy: 'utilisateur', targetEntity: Document::class)]
    private Collection $documents;

    #[ORM\Column(type: 'boolean')]
    private $isVerified = false;

    #[ORM\OneToMany(mappedBy: 'utilisateur', targetEntity: UtilisateursTaches::class, cascade: ["remove"])]
    private Collection $utilisateursTaches;

    #[ORM\Column(length: 255, nullable: true)]
    #[Assert\NotBlank]
    private ?string $imageName = '';


    #[Vich\UploadableField(mapping: "utilisateur", fileNameProperty: "imageName")]
    private ?File $imageFile = null;



    public function __construct()
    {
        $this->commentaires = new ArrayCollection();
        $this->documents = new ArrayCollection();
        $this->utilisateursTaches = new ArrayCollection();
    }


    public function getCin(): ?string
    {
        return $this->cin;
    }

    public function setCin(string $cin): self
    {
        $this->cin = $cin;

        return $this;
    }

    /**
     * A visual identifier that represents this user.
     *
     * @see UserInterface
     */
    public function getUserIdentifier(): string
    {
        return (string) $this->cin;
    }

    /**
     * @see UserInterface
     */
    public function getRoles(): array
    {
        $roles = $this->roles;
        // guarantee every user at least has ROLE_USER
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    public function setRoles(array $roles): self
    {
        $this->roles = $roles;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function eraseCredentials()
    {
        // If you store any temporary, sensitive data on the user, clear it here
        // $this->plainPassword = null;
    }

    /**
     * @return Collection<int, Commentaire>
     */
    public function getCommentaires(): Collection
    {
        return $this->commentaires;
    }

    public function addCommentaire(Commentaire $commentaire): self
    {
        if (!$this->commentaires->contains($commentaire)) {
            $this->commentaires->add($commentaire);
            $commentaire->setUtilisateur($this);
        }

        return $this;
    }

    public function removeCommentaire(Commentaire $commentaire): self
    {
        if ($this->commentaires->removeElement($commentaire)) {
            // set the owning side to null (unless already changed)
            if ($commentaire->getUtilisateur() === $this) {
                $commentaire->setUtilisateur(null);
            }
        }

        return $this;
    }


    public function getPassword(): ?string
    {
        return $this->password;
    }

    public function setPassword(string $password): self
    {
        $this->password = $password;

        return $this;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(?string $email): self
    {
        $this->email = $email;

        return $this;
    }

    public function getNom(): ?string
    {
        return $this->nom;
    }

    public function setNom(?string $nom): self
    {
        $this->nom = $nom;

        return $this;
    }

    public function getPrenom(): ?string
    {
        return $this->prenom;
    }

    public function setPrenom(?string $prenom): self
    {
        $this->prenom = $prenom;

        return $this;
    }

    public function getGenre(): ?string
    {
        return $this->genre;
    }

    public function setGenre(?string $genre): self
    {
        $this->genre = $genre;

        return $this;
    }

    public function getTelephone(): ?string
    {
        return $this->telephone;
    }

    public function setTelephone(?string $telephone): self
    {
        $this->telephone = $telephone;

        return $this;
    }

    public function getProfil(): ?Profil
    {
        return $this->profil;
    }

    public function getProfilTitre(): ?string{
        if ($this->profil){
            return $this->profil->getTitreProfil();
        }
        return "";
    }

    public function setProfil(?Profil $profil): self
    {
        $this->profil = $profil;

        return $this;
    }

    public function getDepartement(): ?Departement
    {
        return $this->departement;
    }

    public function setDepartement(?Departement $departement): self
    {
        $this->departement = $departement;

        return $this;
    }

    public function getPole(): ?Pole
    {
        return $this->pole;
    }

    public function setPole(?Pole $pole): self
    {
        $this->pole = $pole;

        return $this;
    }

    public function getDomaine(): ?Domaine
    {
        return $this->domaine;
    }

    public function setDomaine(?Domaine $domaine): self
    {
        $this->domaine = $domaine;

        return $this;
    }

    /**
     * @return Collection<int, Document>
     */
    public function getDocuments(): Collection
    {
        return $this->documents;
    }

    public function addDocument(Document $document): self
    {
        if (!$this->documents->contains($document)) {
            $this->documents->add($document);
            $document->setUtilisateur($this);
        }

        return $this;
    }

    public function removeDocument(Document $document): self
    {
        if ($this->documents->removeElement($document)) {
            // set the owning side to null (unless already changed)
            if ($document->getUtilisateur() === $this) {
                $document->setUtilisateur(null);
            }
        }

        return $this;
    }

    public function isVerified(): bool
    {
        return $this->isVerified;
    }

    public function setIsVerified(bool $isVerified): self
    {
        $this->isVerified = $isVerified;

        return $this;
    }

    /**
     * @return Collection<int, UtilisateursTaches>
     */
    public function getUtilisateursTaches(): Collection
    {
        return $this->utilisateursTaches;
    }

    public function addUtilisateursTach(UtilisateursTaches $utilisateursTach): self
    {
        if (!$this->utilisateursTaches->contains($utilisateursTach)) {
            $this->utilisateursTaches->add($utilisateursTach);
            $utilisateursTach->setUtilisateur($this);
        }

        return $this;
    }

    public function removeUtilisateursTach(UtilisateursTaches $utilisateursTach): self
    {
        if ($this->utilisateursTaches->removeElement($utilisateursTach)) {
            // set the owning side to null (unless already changed)
            if ($utilisateursTach->getUtilisateur() === $this) {
                $utilisateursTach->setUtilisateur(null);
            }
        }

        return $this;
    }

    public function setImageName(?string $imageName): self
    {
        $this->imageName = $imageName;
        return $this;

    }

    public function setImageFile(?File $imageFile): self
    {
        $this->imageFile = $imageFile;
        return $this;

    }

    public function getImageName(): ?string
    {
        return $this->imageName;
    }

    public function getImageFile(): ?File
    {
        return $this->imageFile;
    }
}

login in Flutter

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';



class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();

}

class _LoginPageState extends State<LoginPage> {
  final TextEditingController _cinController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();
  String _message = '';

  Future<void> _login() async {
    String cin = _cinController.text;
    String password = _passwordController.text;

    final response = await http.post(
      Uri.parse('http://192.168.17.169:8000/api/login_check'), // Utilisez le bon endpoint pour la connexion
      headers: {'Content-Type': 'application/json'}, // Ajoutez l'en-tête de contenu JSON
      body: jsonEncode({'cin': cin, 'password': password}), // Encodez les données en JSON
    );

    if (response.statusCode == 200) {
      final responseData = jsonDecode(response.body);
      String token = responseData['token'];

      // Stockez le token dans les SharedPreferences
      SharedPreferences prefs = await SharedPreferences.getInstance();
      prefs.setString('token', token);

      Navigator.pushReplacementNamed(
        context,
        '/welcome',
      );
    } else {
      setState(() {
        _message = 'Invalid cin or password';
      });
    }
  }



  @override
  Widget build(BuildContext context) {
    //code.................
  }
}

Retreive Roles from token :

import 'package:jwt_decoder/jwt_decoder.dart'; // Importez les dépendances nécessaires

List<String> getUserRolesFromToken(String jwtToken) {
  Map<String, dynamic> decodedToken = JwtDecoder.decode(jwtToken);
  dynamic roles = decodedToken['roles']; // Replace 'user_roles' with the actual key for the roles in your JWT token payload

  if (roles is List<dynamic>) {
    return roles.map((role) => role.toString()).toList();
  } else {
    return [];
  }
}

____

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'side_menu.dart';
import 'package:dembe/helpers/accessUser.dart';

class profil extends StatelessWidget {
  Future<String?> getToken() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    return prefs.getString('token');
  }



  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      drawer: SideMenu(), // Include the SideMenu widget here
      body: Center(
        child: FutureBuilder<String?>(
          future: getToken(),
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return CircularProgressIndicator(); // Afficher un indicateur de chargement en attendant que le token soit récupéré
            }
            else if (snapshot.hasError) {
              return Text('Error: ${snapshot.error}');
            } else if (snapshot.hasData && snapshot.data != null && snapshot.data!.isNotEmpty) {
              String token = snapshot.data!;
              List<String> userRoles = getUserRolesFromToken(token);
              String rolesString = userRoles.join(', '); // Convert the list to a comma-separated string
              
                return Text('Token: $token\nUser Role: $rolesString');
            }
            else {
              // Gérer le cas où le token est null ou vide
              return Text('Token not available');
            }
          },
        ),
      ),
    );
  }
  
}

Solution

  • you're using this bundle for generate jwt. However, this library makes jwt payload only with roles and username (default).

    If you want to add custom field, please refer this article

    and prefs.setString is Future, so you have to use setString using await

    await prefs.setString('token', token);