2

I'm using Symfony 4 "Custom Authentication System with Guard (API Token Example)"Custom Authentication System with Guard (API Token Example)

I want to generate api token when user register from other app(i.e Advance Rest Client) and then want to use this token to access other api's like get articles list.

There is no option to generate token or may be I can't find out.

Here is my code:

config/packages/security.yaml

security:
encoders:
    App\Entity\User:
        algorithm: bcrypt

# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
    # used to reload user from session & other features (e.g. switch_user)
    app_user_provider:
        entity:
            class: App\Entity\User
            #property: email
firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
    api:
        pattern:    ^/api/
        stateless:  true
        anonymous:  false
        guard:
            authenticators:
                - App\Security\TokenAuthenticator
    main:
        pattern: ^/
        anonymous: true
        guard:
            authenticators:
                - App\Security\LoginFormAuthenticator
        logout:
            path:   app_logout

        # activate different ways to authenticate

        # http_basic: true
        # https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate

        # form_login: true
        # https://symfony.com/doc/current/security/form_login_setup.html

# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
    - { path: ^/api, roles: [ IS_AUTHENTICATED_FULLY ] }
    - { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/admin, roles: ROLE_ADMIN }
    # - { path: ^/profile, roles: ROLE_USER }

Controller/RegistrationController.php

class RegistrationController extends AbstractController{

/**
 * @Route("/register/api", name="app_register_api")
 */
public function registerApi(Request $request, UserPasswordEncoderInterface $passwordEncoder, GuardAuthenticatorHandler $guardHandler, TokenAuthenticator $authenticator){

    $user = new User();
    $form = $this->createForm(RegistrationFormType::class, $user);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        // encode the plain password
        $user->setPassword(
            $passwordEncoder->encodePassword(
                $user,
                $form->get('plainPassword')->getData()
            )
        );

        $entityManager = $this->getDoctrine()->getManager();
        $entityManager->persist($user);
        $entityManager->flush();

        // do anything else you need here, like send an email


        $data = $guardHandler->authenticateUserAndHandleSuccess(
            $user,
            $request,
            $authenticator,
            'api' // firewall name in security.yaml
        );

        return new JsonResponse($data, Response::HTTP_FORBIDDEN);
    }

}

}

Security/TokenAuthenticator.php

class TokenAuthenticator extends AbstractGuardAuthenticator
{
  private $em;

public function __construct(EntityManagerInterface $em)
{
    $this->em = $em;
}

/**
 * Called on every request to decide if this authenticator should be
 * used for the request. Returning false will cause this authenticator
 * to be skipped.
 */
public function supports(Request $request)
{
    return $request->headers->has('X-AUTH-TOKEN');
}

/**
 * Called on every request. Return whatever credentials you want to
 * be passed to getUser() as $credentials.
 */
public function getCredentials(Request $request)
{
    return [
        'token' => $request->headers->get('X-AUTH-TOKEN'),
    ];
}

public function getUser($credentials, UserProviderInterface $userProvider)
{
    $apiToken = $credentials['token'];

    if (null === $apiToken) {
        return;
    }

    // if a User object, checkCredentials() is called
    return $this->em->getRepository(User::class)
        ->findOneBy(['apiToken' => $apiToken]);
}

public function checkCredentials($credentials, UserInterface $user)
{
    // check credentials - e.g. make sure the password is valid
    // no credential check is needed in this case

    // return true to cause authentication success
    return true;
}

public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
    // on success, let the request continue
    return null;

}

public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
    $data = [
        'message' => strtr($exception->getMessageKey(), $exception->getMessageData())

        // or to translate this message
        // $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
    ];

    return new JsonResponse($data, Response::HTTP_FORBIDDEN);
}

/**
 * Called when authentication is needed, but it's not sent
 */
public function start(Request $request, AuthenticationException $authException = null)
{
    $data = [
        // you might translate this message
        'message' => 'Authentication Required'
    ];

    return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}

public function supportsRememberMe()
{
    return false;
}
}

Next problem is how to get user detail from token in articles api?

<?php

  namespace App\Controller\Rest;

  use Symfony\Component\HttpFoundation\Request;
  use Symfony\Component\HttpFoundation\Response;
  use FOS\RestBundle\Controller\FOSRestController;
  use FOS\RestBundle\Controller\Annotations as Rest;
  use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

  use App\Entity\Article;
  use App\Form\ArticleType;
  use App\Service\ArticleService;

  class ArticleController extends FOSRestController
  {
     /**
          * @var ArticleService
    */
      private $articleService;
     /**
     * ArticleController constructor.
     * @param ArticleService $articleService
     */
     public function __construct(ArticleService $articleService)
    {
      $this->articleService = $articleService;
    }

/**
* Lists all Articles.
* http://sf4.local/api/articles
* @Rest\Get("/articles")
*
* @return Response
*/
public function getArticles()
 {
    $items = $this->articlesService->getAllArticles();
    return $this->handleView($this->view($items));
 }
}
Muhammad Shahzad
  • 9,340
  • 21
  • 86
  • 130
  • should just be `$user = $this->getUser()` in the controller. – Jakumi Apr 21 '19 at 11:27
  • No user info in $user object when I print $user. I think the reason is I have set in configuration api stateless. – Muhammad Shahzad Apr 21 '19 at 12:53
  • stateless shouldn't be the issue. are you actually accessing a uri starting with /api ? otherwise the authentication shouldn't even trigger..... – Jakumi Apr 21 '19 at 19:47
  • Yes I'm accessing url with starting api i.e ```http://sf4.local/api/contents``` – Muhammad Shahzad Apr 21 '19 at 20:10
  • well ... it should work according to this: https://symfony.com/doc/current/security.html#retrieving-the-user-object I'm actually out of ideas tbh. you should probably look in the profiler, what the security component does. I very much could imagine, that the user auth doesn't work ... or the credentials are wrong and therefor the user object is null. – Jakumi Apr 21 '19 at 20:40
  • okay thanks but my main issue is how to return api token when user register, in registerApi function ``` $data = $guardHandler->authenticateUserAndHandleSuccess( $user, $request, $authenticator, 'api' // firewall name in security.yaml );``` I'm returning $data but it shows null. – Muhammad Shahzad Apr 21 '19 at 20:47
  • wait ... what? oh ... I was confused. well ... I guess you have to generate it. have a look at https://php.net/random_bytes ... store that and done. – Jakumi Apr 21 '19 at 20:52
  • Oh, so I need to do it myself, I was thinking there is some built in method to generate token. – Muhammad Shahzad Apr 21 '19 at 20:55
  • there might be. but an api-token is essentially some sufficiently random string. – Jakumi Apr 21 '19 at 20:58
  • I will keep open this question may be someone can explain in detail :) – Muhammad Shahzad Apr 23 '19 at 09:01
  • 1
    have a look at https://stackoverflow.com/questions/14412132/best-approach-for-generating-api-key (1st and 4th answer - the both top voted answers). I think they're both helpful. – Jakumi Apr 23 '19 at 09:34
  • You could also have a look at JsonWebTokens, there's a bundle for that as well. I already used it with my custom guard auth systems and its great : https://github.com/lexik/LexikJWTAuthenticationBundle – Elbarto May 23 '19 at 14:23

0 Answers0