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');
}
},
),
),
);
}
}
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);