Giter Club home page Giter Club logo

fos-rest-and-user-bundle-integration's Introduction

FOS User Bundle with FOS REST Bundle integration

Full course available at : https://codereviewvideos.com/course/fosuser-fosrest-lexikjwt-user-login-registration-api

In this course we are going to create ourselves a RESTful user Registration and Login system using a combination of:

  • FOSUserBundle
  • FOSRESTBundle
  • LexikJWTBundle

We will combine all three together to expose the numerous features provided by FOSUserBundle via a RESTful API. This will include user registration, user login, managing and maintaining user profiles, and updating and resetting user passwords.

As we are going to make use of FOSUserBundle, we will keep all the features that make this bundle so attractive - from user management via Symfony console commands, through to well thought out email notifications, and a full range of translations.

Certain parts will need tweaking, and for this we will need to override some of the functionality provided out-of-the-box by FOSUserBundle, to make it work in the way we expect.

Speaking of this, we will need to ensure our RESTful user login and registration system is well tested. For this we will make use of Behat, writing a full suite of tests to cover the various flows:

  • Login
  • Password Management
  • Profile Updates
  • Registration

We will also need to override part of the mail flow, so for this we will add in our own mailer service.

Along the way we will enhance the output given to us by default from LexikJWTBundle. We will add in additional data to the JWT it creates for us, allowing us to customise the created token as our system requires.

Whilst this system isn't the most visually appealing - it is a RESTful API after all - the functionality it provides will enable us to do some really interesting things.

Immediately following this course will be a subsequent course on how to implement a React & Redux powered login / registration front-end, making full use of the code found in this course.

Speaking of which, the code is immediately available. You can pull this code today and start playing with it. You can customise it, tweak it, change it to better meet your needs, or simply use it as a reference. Pull requests are always welcome, should you feel the code can be improved.

Lastly, before we start, I want to make you fully aware of one thing:

This is a suggested / example implementation. I am not for one moment suggesting this is the way to be approaching this problem. This code works for me and my needs. It may not work for you and yours. Please use your own careful judgement.

With that said, let's start by seeing the 'finished' application in action, and then learn how it is all put together.

P.s. - I say 'finished' as this is work in progress. There will very likely be extensions / bonus modules to this course which enhance the functionality. An example of this may be to add in billing with third party services, such as Stripe.

fos-rest-and-user-bundle-integration's People

Contributors

a6software avatar celvin avatar codereviewvideos avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

fos-rest-and-user-bundle-integration's Issues

Newer RestApiContext

<?php

namespace AppBundle\Features\Context;

use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7;
use PHPUnit_Framework_Assert as Assertions;
use Behatch\Json\JsonInspector;
use Behatch\Json\JsonSchema;
use Symfony\Component\HttpFoundation\Request;

/**
 * Class RestApiContext
 * @package AppBundle\Features\Context
 */
class RestApiContext implements Context
{
    /**
     * @var ClientInterface
     */
    protected $client;

    /**
     * @var string
     */
    private $authorization;

    /**
     * @var array
     */
    private $headers = [];

    /**
     * @var \GuzzleHttp\Message\RequestInterface
     */
    private $request;

    /**
     * @var \GuzzleHttp\Message\ResponseInterface
     */
    private $response;

    /**
     * @var array
     */
    private $placeHolders = array();
    /**
     * @var
     */
    private $dummyDataPath;

    /**
     * RestApiContext constructor.
     * @param ClientInterface   $client
     */
    public function __construct(ClientInterface $client, $dummyDataPath = null)
    {
        $this->client = $client;
        $this->dummyDataPath = $dummyDataPath;

        // strangeness with guzzle?
        $this->addHeader('accept', '*/*');
    }

    /**
     * Adds Basic Authentication header to next request.
     *
     * @param string $username
     * @param string $password
     *
     * @Given /^I am authenticating as "([^"]*)" with "([^"]*)" password$/
     */
    public function iAmAuthenticatingAs($username, $password)
    {
        $this->removeHeader('Authorization');
        $this->authorization = base64_encode($username . ':' . $password);
        $this->addHeader('Authorization', 'Basic ' . $this->authorization);
    }

    /**
     * Adds JWT Token to Authentication header for next request
     *
     * @param string $username
     * @param string $password
     *
     * @Given /^I am successfully logged in with username: "([^"]*)", and password: "([^"]*)"$/
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function iAmSuccessfullyLoggedInWithUsernameAndPassword($username, $password)
    {
        try {

            $this->iSendARequest('POST', 'login', [
                'json' => [
                    'username' => $username,
                    'password' => $password,
                ]
            ]);

            $this->theResponseCodeShouldBe(200);

            $responseBody = json_decode($this->response->getBody(), true);
            $this->addHeader('Authorization', 'Bearer ' . $responseBody['token']);

        } catch (RequestException $e) {

            echo Psr7\str($e->getRequest());

            if ($e->hasResponse()) {
                echo Psr7\str($e->getResponse());
            }

        }
    }

    /**
     * @When I have forgotten to set the :header
     */
    public function iHaveForgottenToSetThe($header)
    {
        $this->addHeader($header, null);
    }

    /**
     * Sets a HTTP Header.
     *
     * @param string $name  header name
     * @param string $value header value
     *
     * @Given /^I set header "([^"]*)" with value "([^"]*)"$/
     */
    public function iSetHeaderWithValue($name, $value)
    {
        $this->addHeader($name, $value);
    }

    /**
     * Sends HTTP request to specific relative URL.
     *
     * @param string $method request method
     * @param string $url    relative url
     * @param array  $data
     *
     * @When /^(?:I )?send a "([A-Z]+)" request to "([^"]+)"$/
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function iSendARequest($method, $url, array $data = [])
    {
        $url = $this->prepareUrl($url);
        $data = $this->prepareData($data);

        try {
            $this->response = $this->getClient()->request($method, $url, $data);
        } catch (RequestException $e) {
            if ($e->hasResponse()) {
                $this->response = $e->getResponse();
            }
        }
    }

    /**
     * Sends HTTP request to specific URL with field values from Table.
     *
     * @param string    $method request method
     * @param string    $url    relative url
     * @param TableNode $post   table of post values
     *
     * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with values:$/
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function iSendARequestWithValues($method, $url, TableNode $post)
    {
        $url = $this->prepareUrl($url);
        $fields = array();

        foreach ($post->getRowsHash() as $key => $val) {
            $fields[$key] = $this->replacePlaceHolder($val);
        }

        $bodyOption = array(
            'body' => json_encode($fields),
        );
        $this->request = $this->getClient()->request($method, $url, $bodyOption);
        if (!empty($this->headers)) {
            $this->request->addHeaders($this->headers);
        }

        $this->sendRequest();
    }

    /**
     * Sends HTTP request to specific URL with raw body from PyString.
     *
     * @param string       $method request method
     * @param string       $url    relative url
     * @param PyStringNode $string request body
     *
     * @When /^(?:I )?send a "([A-Z]+)" request to "([^"]+)" with body:$/
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function iSendARequestWithBody($method, $url, PyStringNode $string)
    {
        $url = $this->prepareUrl($url);
        $string = $this->replacePlaceHolder(trim($string));

        $this->request = $this->iSendARequest(
            $method,
            $url,
            [ 'body' => $string, ]
        );
    }

    /**
     * Sends HTTP request to specific URL with form data from PyString.
     *
     * @param string       $method request method
     * @param string       $url    relative url
     * @param PyStringNode $body   request body
     *
     * @When /^(?:I )?send a "([A-Z]+)" request to "([^"]+)" with form data:$/
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function iSendARequestWithFormData($method, $url, PyStringNode $body)
    {
        $url = $this->prepareUrl($url);
        $body = $this->replacePlaceHolder(trim($body));

        $fields = array();
        parse_str(implode('&', explode("\n", $body)), $fields);
        $this->request = $this->getClient()->request($method, $url, []);
        /** @var \GuzzleHttp\Post\PostBodyInterface $requestBody */
        $requestBody = $this->request->getBody();
        foreach ($fields as $key => $value) {
            $requestBody->setField($key, $value);
        }

        $this->sendRequest();
    }

    /**
     * @When /^(?:I )?send a multipart "([A-Z]+)" request to "([^"]+)" with form data:$/
     * @param           $method
     * @param           $url
     * @param TableNode $post
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     * @throws \DomainException
     */
    public function iSendAMultipartRequestToWithFormData($method, $url, TableNode $post)
    {
        $url = $this->prepareUrl($url);

        $fileData = $post->getColumnsHash()[0];

        if ( ! array_key_exists('filePath', $fileData)) {
            throw new \DomainException('Multipart requests require a `filePath` Behat table node');
        }

        $filePath = $this->dummyDataPath . $fileData['filePath'];
        unset($fileData['filePath']);

        $data['multipart'] = [
            [
                'name'      => 'name', // symfony form field name
                'contents'  => $fileData['name'],
            ],
            [
                'name'      => 'uploadedFile', // symfony form field name
                'contents'  => fopen($filePath, 'rb'),
            ]
        ];

        // remove the Content-Type header here as it will have been set to `application/json` during the successful
        // login, that preceeds this step in the Behat Background setup
        $this->removeHeader('Content-Type');
        $data = $this->prepareData($data);

        try {
            $this->response = $this->getClient()->request($method, $url, $data);
        } catch (RequestException $e) {
            if ($e->hasResponse()) {
                $this->response = $e->getResponse();
            }
        }
    }

    /**
     * Checks that response has specific status code.
     *
     * @param string $code status code
     *
     * @Then the response code should be :arg1
     */
    public function theResponseCodeShouldBe($code)
    {
        $expected = (int)$code;
        $actual = (int)$this->response->getStatusCode();
        Assertions::assertSame($expected, $actual);
    }

    /**
     * Checks that response body contains specific text.
     *
     * @param string $text
     *
     * @Then /^(?:the )?response should contain "((?:[^"]|\\")*)"$/
     */
    public function theResponseShouldContain($text)
    {
        $expectedRegexp = '/' . preg_quote($text) . '/i';
        $actual = (string) $this->response->getBody();
        Assertions::assertRegExp($expectedRegexp, $actual);
    }

    /**
     * Checks that response body doesn't contains specific text.
     *
     * @param string $text
     *
     * @Then /^(?:the )?response should not contain "([^"]*)"$/
     */
    public function theResponseShouldNotContain($text)
    {
        $expectedRegexp = '/' . preg_quote($text) . '/';
        $actual = (string) $this->response->getBody();
        Assertions::assertNotRegExp($expectedRegexp, $actual);
    }

    /**
     * Checks that response body contains JSON from PyString.
     *
     * Do not check that the response body /only/ contains the JSON from PyString,
     *
     * @param PyStringNode $jsonString
     *
     * @throws \RuntimeException
     *
     * @Then /^(?:the )?response should contain json:$/
     */
    public function theResponseShouldContainJson(PyStringNode $jsonString)
    {
        $etalon = json_decode($this->replacePlaceHolder($jsonString->getRaw()), true);
        $actual = json_decode($this->response->getBody(), true);

        if (null === $etalon) {
            throw new \RuntimeException(
                "Can not convert etalon to json:\n" . $this->replacePlaceHolder($jsonString->getRaw())
            );
        }

        Assertions::assertGreaterThanOrEqual(count($etalon), count($actual));
        foreach ($etalon as $key => $needle) {
            Assertions::assertArrayHasKey($key, $actual);
            Assertions::assertEquals($etalon[$key], $actual[$key]);
        }
    }

    /**
     * Prints last response body.
     *
     * @Then print response
     */
    public function printResponse()
    {
        $response = $this->response;

        echo sprintf(
            "%d:\n%s",
            $response->getStatusCode(),
            $response->getBody()
        );
    }

    /**
     * @Then the response header :header should be equal to :value
     */
    public function theResponseHeaderShouldBeEqualTo($header, $value)
    {
        $header = $this->response->getHeaders()[$header];
        Assertions::assertContains($value, $header);
    }

    /**
     * Prepare URL by replacing placeholders and trimming slashes.
     *
     * @param string $url
     *
     * @return string
     */
    private function prepareUrl($url)
    {
        return ltrim($this->replacePlaceHolder($url), '/');
    }

    /**
     * Sets place holder for replacement.
     *
     * you can specify placeholders, which will
     * be replaced in URL, request or response body.
     *
     * @param string $key   token name
     * @param string $value replace value
     */
    public function setPlaceHolder($key, $value)
    {
        $this->placeHolders[$key] = $value;
    }

    /**
     * @Then I follow the link in the Location response header
     */
    public function iFollowTheLinkInTheLocationResponseHeader()
    {
        $location = $this->response->getHeader('Location')[0];

        if ( ! $this->hasHeader('Authorization')) {
            $responseBody = json_decode($this->response->getBody(), true);
            $this->addHeader('Authorization', 'Bearer ' . $responseBody['token']);
        }

        $this->iSendARequest(Request::METHOD_GET, $location);
    }

    /**
     * @Then the JSON should be valid according to this schema:
     */
    public function theJsonShouldBeValidAccordingToThisSchema(PyStringNode $schema)
    {
        $inspector = new JsonInspector('javascript');

        $json = new \Behatch\Json\Json($this->response->getBody());

        $inspector->validate(
            $json,
            new JsonSchema($schema)
        );
    }

    /**
     * Checks, that given JSON node is equal to given value
     *
     * @Then the JSON node :node should be equal to :text
     * @throws \Exception
     */
    public function theJsonNodeShouldBeEqualTo($node, $text)
    {
        $json = new \Behatch\Json\Json($this->response->getBody());

        $inspector = new JsonInspector('javascript');

        $actual = $inspector->evaluate($json, $node);

        if ($actual != $text) {
            throw new \Exception(
                sprintf("The node value is '%s'", json_encode($actual))
            );
        }
    }

    /**
     * Replaces placeholders in provided text.
     *
     * @param string $string
     *
     * @return string
     */
    protected function replacePlaceHolder($string)
    {
        foreach ($this->placeHolders as $key => $val) {
            $string = str_replace($key, $val, $string);
        }

        return $string;
    }

    /**
     * Returns headers, that will be used to send requests.
     *
     * @return array
     */
    protected function getHeaders()
    {
        return $this->headers;
    }

    /**
     * Adds header
     *
     * @param string $name
     * @param string $value
     */
    protected function addHeader($name, $value)
    {
        if ( ! $this->hasHeader($name)) {
            $this->headers[$name] = $value;
        }

        if (!is_array($this->headers[$name])) {
            $this->headers[$name] = [$this->headers[$name]];
        }

        $this->headers[$name] = $value;
    }

    protected function hasHeader($name)
    {
        return isset($this->headers[$name]);
    }

    /**
     * Removes a header identified by $headerName
     *
     * @param string $headerName
     */
    protected function removeHeader($headerName)
    {
        if (array_key_exists($headerName, $this->headers)) {
            unset($this->headers[$headerName]);
        }
    }

    /**
     * @return ClientInterface
     */
    private function getClient()
    {
        if (null === $this->client) {
            throw new \RuntimeException('Client has not been set in WebApiContext');
        }

        return $this->client;
    }

    private function prepareData($data)
    {
        if (!empty($this->headers)) {
            $data = array_replace(
                $data,
                ["headers" => $this->headers]
            );
        }

        return $data;
    }
}

The CSRF token is invalid. Please try to resubmit the form

Hello,
i'm using your example with FOSRestBundle, FOSUserBundle and PUGXMultiuserBundle
when i'm tried to test the registration of new user with postman i have always this error
The CSRF token is invalid. Please try to resubmit the form
and in my "registerAction" method i put it this code :
$form = $formFactory->createForm([ 'csrf_protection' => false ]);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.