We will make the backend of the application with the framework Symfony. We will have two entities, User and Task. Access to the login will be via Jwt
- Project Creation
- Database Configuration
- Entities
- Routing
- Installing Vue, SASS and bootstrap 4 using WebPackEncore
- Creating a JSON Response
- Login JWT
- Other Requests to the Api
- We will create the project through the console command:
composer create-project symfony/skeleton symfony
-
Server Component,
composer require server --dev
-
Profiler Component,
composer require --dev profiler
-
Apache-Pack Component,
composer require symfony/apache-pack
-
Var-dumper Component,
composer require symfony/var-dumper
. Note If using it inside a Symfony application, make sure that the DebugBundle has been installed (or runcomposer require symfony/debug-bundle
to install it). -
Debug-Bundle Component,
composer require debug --dev
-
The Config Component,
composer require symfony/config
. -
composer require dependency-injection
-
Twig Component,
composer require twig
-
Twig Extension Component,
composer require twig/extensions
-
Asset component,
composer require symfony/asset
-
Knplabs/knp-paginator-bundle,
composer require knplabs/knp-paginator-bundle
-
Doctrine Component,
composer require doctrine
-
Security Component,
composer require security
-
Validator Component,
composer require validator
-
Form Componente,
composer require form
-
Firebase/php-jwt,
composer require firebase/php-jwt
-
The HttpFoundation Component,
composer require symfony/http-foundation
. -
The Serializer Component,
composer require symfony/serializer
-
Mailer Component,
composer require mailer
-
Check Requirements for Running Symfony Component,
composer require symfony/requirements-checker
php bin/console server:run
php bin/console doctrine:database:create
php bin/console doctrine:schema:update --force
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
php bin/console make:controller Default
- Created our project using the Console command's,
composer create-project symfony/skeleton symfony
- In the next step we will access the project folder using:
cd symfony
- It is necessary to install the server component, to use our Server Local, through the console command:
composer require server --dev
- Now, we are going to install de profiler component to can use the debug toolbar of symfony:
composer require --dev profiler
- Now, you will be able to view the result of demo when write in the terminal the command console:
php bin/console server:run
- Access to http://127.0.0.1:8000/ to view the result.
- We will installed Doctrine Component to manage the database of project using the command:
composer require doctrine
- To configurate the database connection, we will modified the environment variable called
DATABASE_URL
. For then, we you can find and customize this inside .env:
# .env
###> doctrine/doctrine-bundle ###
# Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# Configure your db driver and server_version in config/packages/doctrine.yaml
# DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
++ DATABASE_URL=mysql://root:@127.0.0.1:3306/app-cinemas
++ # db_user: root
++ # db_password:
++ # db_name: app-cinemas
# to use sqlite:
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"
###< doctrine/doctrine-bundle ###
(Source: https://symfony.com/doc/current/doctrine.html#configuring-the-database)
-
In console lunch the command
php bin/console doctrine:database:create
. Now you have your data base created in your local server. -
If you want to use XML instead of
yaml
, addtype: yml
anddir: '%kernel.project_dir%/config/doctrine'
to the entity mappings in your config/packages/doctrine.yaml file.
doctrine:
# ...
orm:
# ...
mappings:
App:
is_bundle: false
# type: annotation
type: yml
# dir: '%kernel.project_dir%/src/Entity'
dir: '%kernel.project_dir%/config/doctrine'
prefix: 'App\Entity'
alias: App
(Source: https://symfony.com/doc/current/doctrine.html)
- Now, We can generate our user and task entities.
App\Entity\Users:
type: entity
#repositoryClass: App\Repository\UserRepository
table: users
id:
id:
type: integer
nullable: false
options:
unsigned: false
id: true
generator:
strategy: IDENTITY
fields:
role:
type: json_array
nullable: false
length: null
options:
fixed: false
username:
type: string
nullable: true
length: 80
options:
fixed: false
name:
type: string
nullable: true
length: 180
options:
fixed: false
surname:
type: string
nullable: true
length: 255
options:
fixed: false
email:
type: string
nullable: false
length: 254
options:
fixed: false
plainPassword:
type: text
length: 4096
column: plain_password
password:
type: string
length: 64
isActive:
type: boolean
nullable: false
options:
default: '0'
column: is_active
createdAt:
type: datetime
nullable: false
column: created_at
updatedAt:
type: datetime
nullable: false
column: updated_at
lifecycleCallbacks: { }
We need use the security-bundle of Symfony to extends the class user (
class Users implements UserInterface {}
), for it execute the commandcomposer require symfony/security-bundle
.
<?php
// src/Entity/User.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
class Users implements UserInterface {
private $id;
public function getId() { return $this->id; }
private $username;
public function getUsername() { return $this->username; }
public function setUsername($username=null) { $this->username = $username; }
private $name;
public function getName() { return $this->name; }
public function setName($name) { $this->name = $name; }
private $surname;
public function setSurname($surname) { $this->surname = $surname; return $this; }
public function getSurname() { return $this->surname; }
private $email;
public function setEmail($email) { $this->email = $email; return $this; }
public function getEmail() { return $this->email; }
private $plainPassword;
public function getPlainPassword() { return $this->plainPassword; }
public function setPlainPassword($password) { $this->plainPassword = $password; }
/**
* The below length depends on the "algorithm" you use for encoding
* the password, but this works well with bcrypt.
*/
private $password;
public function getPassword() { return $this->password; }
public function setPassword($password) { $this->password = $password; }
private $isActive;
public function getIsActive() { return $this->isActive; }
public function setIsActive($isActive) { $this->isActive = $isActive; }
public function __construct() {
$this->isActive = true;
// may not be needed, see section on salt below
// $this->salt = md5(uniqid('', true));
}
private $createdAt;
public function setCreatedAt($createdAt) { $this->createdAt = $createdAt; return $this; }
public function getCreatedAt() { return $this->createdAt; }
private $updatedAt;
public function setUpdatedAt($updatedAt) { $this->updatedAt = $updatedAt; return $this; }
public function getUpdatedAt() { return $this->updatedAt; }
/* other necessary methods ***********************************************************************************/
public function getSalt() {
// The bcrypt and argon2i algorithms don't require a separate salt.
// You *may* need a real salt if you choose a different encoder.
return null;
}
// other methods, including security methods like getRoles()
private $role;
public function setRole($role) { $this->role = $role; /*return $this;*/ }
public function getRole() { return $this->role; }
public function getRoles(){ return array('ROLE_USER','ROLE ADMIN'); }
// ... and eraseCredentials()
public function eraseCredentials() { }
/** @see \Serializable::serialize() */
public function serialize() {
return serialize(array(
$this->id,
$this->username,
$this->password,
$this->role,
// see section on salt below
// $this->salt,
));
}
public function unserialize($serialized) {
list (
$this->id,
$this->username,
$this->password,
// see section on salt below
// $this->salt
) = unserialize($serialized);
}
}
We can load the database in (/symfony/bbdd/bbdd.sql) using phmyadmin and enter a first example user.
To read the rest of the project entities access the folders /symfony/src/Entity/ and /symfony/config/doctrine/.
- We will use the routing type yaml, for them we configure the type of routing in config/routes.yaml.
#index:
# path: /
# controller: App\Controller\DefaultController::index
# Importante en los archivos con extensión .yaml cada sangrado equivale a 4 espacios!!!
routing_distributed:
# loads routes from the given routing file stored in some bundle
resource: '../src/Resources/config/routing.yaml'
type: yaml
- Next step is defined our folder with routing files of our app in src/Resources/config/routing.yml
src/Resources/config/routing.yml
app_routing_folder:
# loads routes from the given routing file stored in some bundle
prefix: '/api/v1'
resource: 'routing/'
type: directory
src/Resources/config/routing/default.yml
default_pruebas:
path: /test
controller: App\Controller\DefaultController::test
#methods: [POST]
- Now we can test the generated entities and the router. For this we generate a first controller /src/Controller/DefaultController.php with a method
tests()
.
/src/Controller/DefaultController.php
<?php
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use App\Entity\Users;
class DefaultController extends Controller {
public function test (request $request) {
$em = $this->getDoctrine()->getManager();
$user_repo = $em->getRepository(Users::class);
$userList = $user_repo->findAll();
return $this->render('base.html.twig', [
'base_dir' =>realpath($this->getParameter('kernel.project_dir')).DIRECTORY_SEPARATOR,
'user'=>$userList
]);
}
}
- If we run
php bin/console server:run
and access http://127.0.0.1:8000/api/v1/test we can see the existing entity.
- Twig Component,
composer require twig
- Twig Extension Component,
composer require twig/extensions
composer require twig/extensions
(Source: Twig Extension Component)
composer require symfony/asset
(Source: Assets for Twig Component)
We will add the line
<meta name="viewport" content="width=device-width, initial-scale=1.0">
for can use the responsive web in mobiles.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
++ <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}{% endblock %}
</body>
</html>
(Source: CloudWays - Getting Started With Vue.Js In Symfony)
First, make sure you install Node.js and also the NPM package manager.
We can use the commands node -v
and npm -v
to know the version installed in our computer.
Then, install Encore into your project with NPM:
npm install @symfony/webpack-encore --save-dev
If you are using Flex for your project, you can initialize your project for Encore via:
composer require encore
(Source: Symfony Doc - Webpack Encore Component)
This will create a webpack.config.js file, add the assets/ directory, and add node_modules/ to .gitignore.
This command creates (or modifies) a package.json file and downloads dependencies into a node_modules/ directory. When using npm 5, a package-lock.json file is created/updated.
Next, create your webpack.config.js.
// webpack.config.js
var Encore = require('@symfony/webpack-encore');
Encore
// the project directory where all compiled assets will be stored
.setOutputPath('public/build/')
// the public path used by the web server to access the previous directory
.setPublicPath('/build')
// will create public/build/app.js and public/build/app.css
.addEntry('app', './assets/js/app.js')
// allow legacy applications to use $/jQuery as a global variable
.autoProvidejQuery()
// enable source maps during development
.enableSourceMaps(!Encore.isProduction())
// empty the outputPath dir before each build
.cleanupOutputBeforeBuild()
// show OS notifications when builds finish/fail
.enableBuildNotifications()
// create hashed filenames (e.g. app.abc123.css)
// .enableVersioning()
// allow sass/scss files to be processed
// .enableSassLoader()
;
// export the final configuration
module.exports = Encore.getWebpackConfig();
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}
++ <link rel="stylesheet" href="{{ asset('build/app.css') }}">
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}
++ <script src="{{ asset('build/app.js') }}"></script>
{% endblock %}
</body>
</html>
Now, we install Vue and some dependencies:
npm i vue --save-dev
(Source: https://www.npmjs.com/package/vue)
npm i vue-loader --save
(Source: https://www.npmjs.com/package/vue-loader)
npm i vue-template-compiler --save
(Source: https://www.npmjs.com/package/vue-template-compiler)
Then, activate the vue-loader and its plugin in webpack.config.js:
var Encore = require('@symfony/webpack-encore');
++ const { VueLoaderPlugin } = require('vue-loader');
Encore
// the project directory where compiled assets will be stored
.setOutputPath('public/build/')
// the public path used by the web server to access the previous directory
.setPublicPath('/build')
.cleanupOutputBeforeBuild()
.enableSourceMaps(!Encore.isProduction())
// uncomment to create hashed filenames (e.g. app.abc123.css)
// .enableVersioning(Encore.isProduction())
// uncomment to define the assets of the project
// .addEntry('js/app', './assets/js/app.js')
++ .addEntry('js/app', './assets/js/app.js')
// .addStyleEntry('css/app', './assets/css/app.scss')
// uncomment if you use Sass/SCSS files
// .enableSassLoader()
// uncomment for legacy applications that require $/jQuery as a global variable
// .autoProvidejQuery()
++ // Enable Vue loader
++ .enableVueLoader()
++ .addPlugin(new VueLoaderPlugin())
++ ;
;
module.exports = Encore.getWebpackConfig();
Issue enableVueLoader does not include VueLoaderPlugin? #311 - symfony - webpack-encore
We can already create our first component of Vue:
// assets/js/app.js
import Vue from 'vue';
import Example from './components/Example'
/**
* Create a fresh Vue Application instance
*/
new Vue({
el: '#app',
components: {Example}
});
And its component.
/assets/js/components/Example.vue
<template>
<div>
<p>This is an example of a new components in VueJs</p>
</div>
</template>
<script>
export default {
name: "example"
}
</script>
<style scoped>
</style>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('build/app.css') }}">
{% endblock %}
</head>
<body>
++ <div id="app">
{% block body %}{% endblock %}
++ </div>
{% block javascripts %}
<script src="{{ asset('build/app.js') }}"></script>
{% endblock %}
</body>
</html>
/templates/default/vue-example.html.twig
{% extends 'base.html.twig' %}
{% block body %}
<div class="text-center">
<h3>My Symfony 4 sample project</h3>
<div class="jumbotron text-center">
<example></example>
</div>
</div>
{% endblock %}
src/Resources/config/routing/default.yml
# ...
test:
path: /test
controller: App\Controller\DefaultController::test
#methods: [POST]
++ test_vue_example:
++ path: /vue_example
++ controller: App\Controller\DefaultController::vueExample
/src/Controller/DefaultController.php
<?php
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use App\Entity\Users;
class DefaultController extends Controller {
// ...
++ public function vueExample (request $request) {
++ return $this->render('default/vue-example.html.twig');
++ }
}
First, we install Sass and Bootstrap dependencies:
npm i sass-loader --save-dev
(Source: npm - sass-loader)
npm i node-sass --save-dev
(Source: npm - node-sass)
npm i bootstrap --save-dev
(Source: npm - bootstrap)
npm i jquery --save-dev
(Source: npm - jquery)
npm i popper.js --save
(Source: npm - popper)
And we will have to modified the webpack.config.js with the new configuration.
var Encore = require('@symfony/webpack-encore');
const { VueLoaderPlugin } = require('vue-loader');
Encore
// the project directory where compiled assets will be stored
.setOutputPath('public/build/')
// the public path used by the web server to access the previous directory
.setPublicPath('/build')
.cleanupOutputBeforeBuild()
.enableSourceMaps(!Encore.isProduction())
// uncomment to create hashed filenames (e.g. app.abc123.css)
// .enableVersioning(Encore.isProduction())
// uncomment to define the assets of the project
// .addEntry('js/app', './assets/js/app.js')
.addEntry('js/app', './assets/js/app.js')
// .addStyleEntry('css/app', './assets/scss/app.scss')
// uncomment if you use Sass/SCSS files
// .enableSassLoader()
++ .enableSassLoader()
// uncomment for legacy applications that require $/jQuery as a global variable
// .autoProvidejQuery()
// Enable Vue loader
.enableVueLoader()
.addPlugin(new VueLoaderPlugin())
;
;
module.exports = Encore.getWebpackConfig();
Now modified the /assets/js/app.js for include the libraries jquery and bootstrap in our vue app.
// assets/js/app.js
++ import $ from 'jquery';
++ import 'bootstrap';
import Vue from 'vue';
import Example from './components/Example'
/**
* Create a fresh Vue Application instance
*/
new Vue({
el: '#app',
components: {Example}
});
And include the library bootstrap in our style file,
@import '~bootstrap/scss/bootstrap';
$acceVerde: #84a640;
$acceAzul: #396696;
h1 {
color: $acceVerde;
s {
color: $acceAzul;
}
}
To convert data to Json using symfony it is necessary to have the
http-foundation
component ( Source: The HttpFoundation Component ).
composer require symfony/http-foundation
- First we will check the sending method jsonResponse, generating a test in the controller /src/Controller/DefaultController.php.
/src/Controller/DefaultController.php
<?php
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
++ use Symfony\Component\HttpFoundation\JsonResponse;
use App\Entity\User;
class DefaultController extends Controller {
public function tests (Request $request){
$em = $this->getDoctrine()->getManager();
$user_repo = $em->getRepository(User::class);
$userList = $user_repo->findAll();
++ /*
return $this->render('base.html.twig', [
'base_dir' => realpath($this->getParameter('kernel.project_dir')).DIRECTORY_SEPARATOR,
'user'=>$user_repo
]);
++ */
++ return new JsonResponse(array(
++ 'status' => 'succes',
++ 'users' => $user_repo
++ ));
}
}
Although we use the
JsonResponse()
component, in symfony there is a default component which we access through$this->json()
.
- If we run
php bin/console server: run
and access http://127.0.0.1:8000/ we will see that it does not convert correctly. Still we see thatJsonResponse()
does not perform the conversion correctly so we will have to generate a service that performs the conversion correctly.
To create our encoder Json you must execute the command
composer require symfony/serializer
to download the serializer component.
composer require symfony/serializer
<?php
// src/Service/Helpers.php
namespace App\Service;
class Helpers{
public $manager;
public function __construct($manager){
$this->manager = $manager;
}
public function json($data){
$normalizers = array(new \Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer());
//
$encoders = array("json" => new \Symfony\Component\Serializer\Encoder\JsonEncoder());
$serializer = new \Symfony\Component\Serializer\Serializer($normalizers, $encoders);
$json = $serializer->serialize($data, 'json');
$response = new \Symfony\Component\HttpFoundation\Response();
$response->setContent($json);
$response->headers->set('Content-Type', 'application/json');
return $response;
}
public function decoding_json($json){
$params = json_decode($json);
return $params;
}
}
-
GetSetMethodNormalizer
, This normalizer reads the content of the class by calling the "getters" (public methods starting with "get"). It will denormalize data by calling the constructor and the "setters" (public methods starting with "set"). Objects are normalized to a map of property names and values (names are generated removing theget
prefix from the method name and lowercasing the first letter; e.g.getFirstName() -> firstName
). -
Encoders,
JsonEncoder
, This class encodes and decodes data in JSON.
- To use this new service it is necessary to declare it in config/services.yaml.
//...
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
++ App\Service\Helpers:
++ public: true
++ arguments:
++ $manager: '@doctrine.orm.entity_manager'
- Inside the src/Controller/DefaultController.php we will call it that.
/src/Controller/DefaultController.php
<?php
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
++ use App\Service\Helpers;
use App\Entity\User;
class DefaultController extends Controller {
-- public function index (Request $request){
++ public function index (Request $request, Helpers $helpers){
$em = $this->getDoctrine()->getManager();
$user_repo = $em->getRepository(User::class);
$userList = $user_repo->findAll();
++ return $helpers->json($userList);
-- return new JsonResponse(array(
-- 'status' => 'succes',
-- 'users' => $userList
-- ));
}
}
- Create the
function login
within /src/Controller/AuthenticationController.php.
/src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
++ use Symfony\Component\HttpFoundation\JsonResponse;
++ use Symfony\Component\Validator\Constraints as Assert;
++ use App\Service\Helpers;
//...
class AuthenticationController extends Controller {
//..
++ public function login(Request $request, Helpers $helpers){
++ // Receive json by POST
++ $json = $request->get('json', null);
++ // Array to return by default
++ $data = array(
++ 'status' => 'error',
++ 'data' => 'Send json via post !!'
++ );
++ }
++ return $helpers->json($data);
++ }
//..
- We add a new route in src/Resources/config/routing/default.yml.
/src/Resources/config/routing/authentication.yml
authentication_login:
path: /login
controller: App\Controller\AuthenticationController::login
methods: [POST]
- Access to https://app.getpostman.com/ and send a petition to url http://127.0.0.1:8000/api/v1/login
You must receive the following message:
{"status":"error","data":"Send json via post !!"}
.
- We complete the method login so that it looks for the email match within the User entity.
/src/Controller/AuthenticationController.php
//...
class AuthenticationController extends Controller {
//..
public function login(Request $request, Helpers $helpers){
// Receive json by POST
$json = $request->get('json', null);
// Array to return by default
$data = array( 'status' => 'error', 'data' => 'Send json via post !!' );
++ if($json != null){
++ // you make the login
++ // We convert a json to a php object
++ $params = json_decode($json);
++ $email = (isset($params->email)) ? $params->email : null;
++ $password = (isset($params->password)) ? $params->password : null;
++ $emailConstraint = new Assert\Email();
++ $emailConstraint->message = "This email is not valid !!";
++ $validate_email = $this->get("validator")->validate($email, $emailConstraint);
++ if($email != null && count($validate_email) == 0 && $password != null){
++ $data = array(
++ 'status' => 'success',
++ 'data' => 'Login success'
++ );
++ }else{
++ $data = array(
++ 'status' => 'error',
++ 'data' => 'Email or password incorrect'
++ );
++ }
++ }
return $helpers->json($data);
}
//..
- Access to https://app.getpostman.com/ and send a petition to url http://127.0.0.1:8000/api/v1/login using in the body
x-www-form-urlencoded
with keyjson
and value{"email":"admin@admin.com", "password":"1"}
.
You must receive the following message:
{"status":"success","data":"Email or password success"}
.
- we will need to create a new service that authenticates us and generates the token.
<?php
// src/Service/JwtAuth.php
namespace App\Service;
use App\Entity\User;
use Firebase\JWT\JWT;
class JwtAuth{
public $manager;
public $key;
public function __construct($manager){
$this->manager = $manager;
$this->key = 'helloHowIAmTheSecretKey-98439284934829';
}
public function signup($email, $password, $getHash = null){
$user = $this->manager->getRepository(User::class)->findOneBy(array(
"email" => $email,
"password" => $password
));
$signup = (is_object($user))? true : false;
if($signup == true){
// GENERATE TOKEN JWT
$token = array(
"sub" => $user->getId(),
"email" => $user->getEmail(),
"name" => $user->getName(),
"surname" => $user->getSurname(),
"iat" => time(),
"exp" => time() + (7 * 24 * 60 * 60)
);
$jwt = JWT::encode($token, $this->key, 'HS256');
$decoded = JWT::decode($jwt, $this->key, array('HS256'));
$data = ($getHash == null)? $jwt : $decoded ;
}else{
$data = array( 'status' => 'error', 'data' => 'Login failed!!' );
}
return $data;
}
}
You need the library Firebase\JWT, to install execute the command
composer require firebase/php-jwt
.
- To use this new service it is necessary to declare it in config/services.yaml.
//...
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
App\Service\Helpers:
public: true
arguments:
$manager: '$manager: '@doctrine.orm.entity_manager'
++ App\Service\JwtAuth:
++ public: true
++ arguments:
++ $manager: '@doctrine.orm.entity_manager'
- Now we will modify the login method, in /src/Controller/AuthenticationController.php, so that it consults in the database if the user exists and if it exists, it generates a token.
/src/Controller/AuthenticationController.php
//...
use App\Service\Helpers;
++ use App\Service\JwtAuth;
//...
class AuthenticationController extends Controller {
//..
-- public function login(Request $request, Helpers $helpers){
++ public function login(Request $request, Helpers $helpers, JwtAuth $jwt_auth ){
// Receive json by POST
$json = $request->get('json', null);
// Array to return by default
$data = array( 'status' => 'error', 'data' => 'Send json via post !!' );
if($json != null){
// you make the login
// We convert a json to a php object
$params = json_decode($json);
$email = (isset($params->email)) ? $params->email : null;
$password = (isset($params->password)) ? $params->password : null;
$emailConstraint = new Assert\Email();
$emailConstraint->message = "This email is not valid !!";
$validate_email = $this->get("validator")->validate($email, $emailConstraint);
if($email != null && count($validate_email) == 0 && $password != null){
-- $data = array(
-- 'status' => 'success',
-- 'data' => 'Login success'
-- );
++ $jwt_auth = $this->get(JwtAuth::class);
++ if($getHash == null || $getHash == false){
++ $signup = $jwt_auth->signup($email, $pwd);
++ }else{
++ $signup = $jwt_auth->signup($email, $pwd, true);
++ }
++ return new JsonResponse($signup);
}else{
$data = array(
'status' => 'error',
'data' => 'Email or password incorrect'
);
}
}
return $helpers->json($data);
}
//..
IMPORTANT We replace the return command
$this->json($signup);
byreturn new JsonResponse($signup);
to avoid failures in the transformation to json.
- we must generate a new method within the service that checks the token.
//..
class JwtAuth{
//..
++ public function checkToken($jwt, $getIdentity = false){
++ $auth = false;
++ try{
++ $decoded = JWT::decode($jwt, $this->key, array('HS256'));
++ }catch(
++ \UnexpectedValueException $e){ $auth = false;
++ }catch(
++ \DomainException $e){ $auth = false;
++ }
++ if(isset($decoded) && is_object($decoded) && isset($decoded->sub)){ $auth = true; }else{ $auth = false; }
++ if($getIdentity == false){
++ return $auth;
++ }else{
++ return $decoded;
++ }
++ }
//..
- Now, I can post in https://app.getpostman.com/ a new request to url http://127.0.0.1:8000/api/v1/login using in the body
x-www-form-urlencoded
with keyjson
and value{"email":"admin@admin.com", "password":"1"}
to collect the token.
In our case we received:
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImVtYWlsIjoiYWRtaW5AYWRtaW4uY29tIiwibmFtZSI6ImFkbWluIiwic3VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTI1MDk3NTEwLCJleHAiOjE1MjU3MDIzMTB9.UA3f6W2mqzrHCoJNvCqxHW4NmOFe-9sMVfNOXXPW_gY"
- We modify the test method in /src/Controller/DefaultController.php.
/src/Controller/DefaultController.php
//...
use App\Service\Helpers;
use App\Service\JwtAuth;
//...
class DefaultController extends Controller {
public function test (Request $request, Helpers $helpers){
++ $token = $request->get("authorization", null);
++ if ($token){
$em = $this->getDoctrine()->getManager();
$user_repo = $em->getRepository(User::class);
$userList = $user_repo->findAll();
return $helpers->json(array(
'status' => 'success',
'users' => $userList
));
++ } else {
-- return $helpers->json($userList);
++ return $helpers->json(array(
++ 'status' => 'error',
++ 'code' => 400,
++ 'users' => 'Login failed!!!'
++ ));
++ }
}
- Again, I can post in https://app.getpostman.com/ a new request to url http://127.0.0.1:8000/api/v1/test using in the body
x-www-form-urlencoded
with keyauthorization
and value"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImVtYWlsIjoiYWRtaW5AYWRtaW4uY29tIiwibmFtZSI6ImFkbWluIiwic3VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTI1MDk3NTEwLCJleHAiOjE1MjU3MDIzMTB9.UA3f6W2mqzrHCoJNvCqxHW4NmOFe-9sMVfNOXXPW_gY"
to collect the token.
In our case we received:
{"status":"success","users":[{"id":1,"username":"","name":"admin","surname":"admin","email":"admin@admin.com","password":"1","createdAt":{"timezone":{"name":"UTC","transitions":[{"ts":-9223372036854775808,"time":"-292277022657-01-27T08:29:52+0000","offset":0,"isdst":false,"abbr":"UTC"}],"location":{"country_code":"??","latitude":0,"longitude":0,"comments":""}},"offset":0,"timestamp":1522540800},"salt":null,"role":"admin","roles":["ROLE_USER","ROLE ADMIN"]}]}
To be able to access the rest of the requests, you must see the url of the request inside the routing files of the directory /src/Resources/config/routing/ and access the corresponding controller method /src/Controller/.