21
loading...
This website collects cookies to deliver better user experience
GET /api/messages/public
{
"message": "The API doesn't require an access token to share this message."
}
GET /api/messages/protected
{
"message": "The API successfully validated your access token."
}
GET /api/messages/admin
read:admin-messages
permission to access the admin data. This is often referred to as Role-Based Access Control (RBAC).composer create-project symfony/website-skeleton api-symfony-server
cd api-symfony-server
cp .env .env.local
.gitignore
(which Symfony generated). One of the benefits of this file is that it helps to store your credentials outside of code to keep them safe.DATABASE_URL
parameter in .env.local
so that the app uses an SQLite database instead of the PostgreSQL default. To do that, comment out the existing DATABASE_URL
entry and uncomment the SQLite option so that it matches the example below.DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
NOTE: The database will be created in the var directory in the project's root directory and be named data.db
.
symfony serve
CTRL + C
and then hit Enter
php bin/console make:controller APIController
created: src/Controller/APIController.php
created: templates/api/index.html.twig
Success!
Next: Open your new controller class and add some pages!
src/Controller/APIController.php
and update its content with the following:// src/Controller/APIController.php
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/api/messages")
*/
class APIController extends AbstractController
{
/**
* @Route("/public", name="public")
*/
public function publicAction()
: JsonResponse
{
return $this->json(["message" => "The API doesn't require an access token to share this message."], Response::HTTP_OK);
}
/**
* @Route("/protected", name="protected")
*/
public function protectedAction()
: JsonResponse
{
return $this->json(["message" => "The API successfully validated your access token."], Response::HTTP_OK);
}
/**
* @Route("/admin", name="admin")
*/
public function adminAction(): JsonResponse
{
return $this->json(["message" => "The API successfully recognized you as an admin."], Response::HTTP_OK);
}
}
publicAction()
protectedAction()
adminAction()
/public
, /protected
, and /admin
endpoints and returns the appropriate messages, respectively.symfony serve
and open up an API testing tool such as Postman to test each endpoint.The API doesn't require an access token to share this message
, as shown below:The API successfully recognized you as an admin.
:/protected
and /admin
endpoints are exposed to authorized users only. You will start the configuration in the next section.Symfony API Server
for the API and set its identifier to https://localhost:8000
. You are free to use any name and identifier, but if you want to follow this tutorial exactly, you should maintain the values above. Leave the signing algorithm as RS256
and click on the "Create" button to proceed. You will need the values from here later in the tutorial.GET /api/messages/protected
and GET /api/messages/admin
endpoints you will use the JWT authentication bundle for Symfony named auth0/jwt-auth-bundle.CTRL + C
and run the following command to install the bundle using composer:composer require auth0/jwt-auth-bundle:"~4.0"
config/packages/jwt_auth.yaml
. If not, create the file and paste the following content in it:jwt_auth:
domain: "%env(AUTH0_DOMAIN)%"
client_id: "%env(AUTH0_CLIENT_ID)%"
audience: "%env(AUTH0_AUDIENCE)%"
Applications
> Applications
, then selecting the Test Application from the list that matches what you named your API. If you named it the same as in this tutorial, it will be "Symfony API Server (Test Application)". You can also select and use any other applications for your account. But for this tutorial, click on the test application, and you will see a page as shown here:.env.local
file and update the values of the environment variables below:CLIENT_ORIGIN_URL=http://localhost:4040
AUTH0_AUDIENCE=http://localhost:8000
AUTH0_DOMAIN=YOUR_AUTH0_DOMAIN
AUTH0_CLIENT_ID=YOUR_AUTH0_ID
AUTH0_CLIENT_SECRET=YOUR_AUTH0_CLIENT_SECRET
YOUR_AUTH0_DOMAIN
, YOUR_AUTH0_CLIENT_ID
, and YOUR_AUTH0_CLIENT_SECRET
placeholders with the appropriate values as obtained from your Auth0 Dashboard.src
folder and create a new folder named Security
and within the newly created folder, create another one and call it User
. Next, create the user class within the User
folder and name it WebServiceUser.php
. Open the newly created file and paste the following code into it:<?php
namespace App\Security\User;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class WebServiceUser implements
UserInterface, EquatableInterface {
private $roles;
private $jwt;
public function __construct($jwt, $roles) {
$this->roles = $roles;
$this->jwt = $jwt;
}
/**
* @inheritDoc
*/
public function getRoles()
: array {
return $this->roles;
}
/**
* @inheritDoc
*/
public function getPassword()
: ?string {
return null;
}
/**
* @inheritDoc
*/
public function getSalt()
: ?string {
return null;
}
public function isEqualTo(UserInterface $user)
: bool {
if (!$user instanceof WebServiceUser) {
return false;
}
return $this->getUsername() === $user->getUsername();
}
/**
* @inheritDoc
*/
public function getUsername() {
return $this->jwt["email"] ?? $this->jwt["sub"];
}
/**
* @inheritDoc
*/
public function eraseCredentials() {
}
public function getUserIdentifier() {
return $this->jwt["email"] ?? $this->jwt["sub"];
}
}
WebServiceUser
class implements two different interfaces:User
folder and name it WebServiceAnonymousUser.php
. This will return the anonymous user. Use the following content for it:<?php
namespace App\Security\User;
class WebServiceAnonymousUser extends WebServiceUser {
public function __construct() {
parent::__construct(null, ['IS_AUTHENTICATED_ANONYMOUSLY']);
}
public function getUsername() {
return null;
}
}
User
folder and name it WebServiceUserProvider.php
. Once you are done, paste the following code in it:<?php
namespace App\Security\User;
use Auth0\JWTAuthBundle\Security\Core\JWTUserProviderInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Polyfill\Intl\Icu\Exception\NotImplementedException;
class WebServiceUserProvider implements JWTUserProviderInterface {
public function loadUserByJWT($jwt)
: WebServiceUser {
$data = ['sub' => $jwt->sub];
$roles = [];
$roles[] = 'ROLE_OAUTH_AUTHENTICATED';
return new WebServiceUser($data, $roles);
}
public function getAnonymousUser()
: WebServiceAnonymousUser {
return new WebServiceAnonymousUser();
}
public function loadUserByUsername($username) {
throw new NotImplementedException('method not implemented');
}
public function refreshUser(UserInterface $user) {
if (!$user instanceof WebServiceUser) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
: bool {
return $class === 'App\Security\User\WebServiceUser';
}
public function loadUserByIdentifier(string $identifier)
{
throw new NotImplementedException('method not implemented');
}
}
JWTUserProviderInterface
from the Auth0 bundle installed earlier, which specifies the important methods that the WebServiceUserProvider
class must implement. These methods are:loadUserByJWT
: it receives the decoded JWT Access Token and returns a User.getAnonymousUser
: returns an anonymous user that represents an unauthenticated one (usually represented by the role IS_AUTHENTICATED_ANONYMOUSLY
)