Buy
Buy

Yeaaaa! You've done it! You've made it to the tutorial where we get to build a security system with Symfony. This stuff is cool. Seriously, these days, the topic of security is gigantic! Just think about authentication: you might need to build a traditional login form, or a token-based API authentication system, or two-factor authentication or authentication across an API to a Single Sign-On server or something I've never even dreamed of before! For authorization, there are roles, access controls and more.

Woh. So we're going to write some seriously fun code in this tutorial. And it will be especially fun, because there are some new cool toys in Symfony's security system that make it nicer than ever to work with.

Coding Along!

As always, to become a true Symfony security geek... and to obtain the blueprint to the Deathstar, you should definitely code along with me. Download the course code from this page. When you unzip it, you'll find a start/ directory that has the same code that you see here. Follow the README.md file for all the important setup details.

Oh, and if you've been coding along with me in the Symfony series so far, um, you're amazing! But also, be sure to download the new code: I made a few changes since the last tutorial, including upgrading to Symfony 4.1 and improving our fixture system. More on that later.

Anyways, the last setup step will be to open a terminal, move into the project and run:

php bin/console server:run

to start the built in web server. Ok: head back to your browser and open our app by going to http://localhost:8000.

Hello The SpaceBar! Our awesome intergalactic real news site that helps connect alien species across this side of the Milky Way.

Installing Security & Upgrading MakerBundle

Our first goal in this tutorial is to create an authentication system. In other words: a way for the user to login. No matter how you want your users to authenticate - whether it's a login form, API authentication or something crazier - the first step is always the same: brew some coffee or tea. The second step is also always the same: create a User class.

To do this, we're going to use a brand-spanking new feature! Woo! Find your terminal and run:

composer update symfony/maker-bundle

Version 1.7 of MakerBundle comes with a new command that will make our life much easier. Yep, there it is: 1.7. The new command is called make:user - try it:

php bin/console make:user

Ah! It explodes! Of course! Remember: in Symfony 4, our project starts small. If you need a feature, you need to install it. Run:

composer require security

Ah, check it out: this library has a recipe! When Composer finishes... find out what it did by running:

git status

A new config file! Check it out: config/packages/security.yaml. This file is super important. We'll start talking about it soon.

Creating the User Class with make:user

Before we run make:user again, add all the changed files to git and commit with a message about upgrading MakerBundle & adding security:

git add .
git commit -m "Upgraded MakerBundle and added security"

I'm doing this because I want to see exactly what the make:user command does.

Ok already, let's try it!

php bin/console make:user

Call the class User. Second question:

Do you want to store user data in the database

For most apps, this is an easy yes... because most apps store user data in a local database table. But, what if your user data is stored on some other server, like an LDAP server or a single sign-on server? Well, even in those cases, if you want to store any extra information about your users in a local database table, you should still answer yes. Answer "no" only if you don't need to store any user information to your database.

So, "yes" for us! Next: choose one property on your user that will be its unique display name. This can be anything - it's usually an email or username. We'll talk about how it's used later. Choose email.

And, the last question: is our app responsible for checking the user's password? In some apps - like a pure API with only token authentication, users might not even have a password. And even if your users will be able to login with a password, only answer yes if this app will be responsible for directly checking the user's password. If you actually send the password to a third-party server and it checks if it's valid, choose no.

Remember when I mentioned how complex & different modern authentication systems can be? That's why this command exists: to help walk us through exactly want we need.

I'm going to choose "No" for now. We will add a password later, but we'll keep things extra simple to start.

And... we're done! Awesome! This created a User entity, a Doctrine UserRepository for it, and updated the security.yaml file.

Let's check out these changes next!

Leave a comment!

  • 2019-03-07 Christian

    Solution for MySql 5.5 and 5.6:

    Change the innoDB file format to Barracuda (as it is the default since MySql 5.7)

    in my.cnf:
    innodb_large_prefix = 1
    innodb_file_format = Barracuda

    then convert your table to DYNAMIC or COMPRESSED:
    mysql> ALTER TABLE yourtable ROW_FORMAT=DYNAMIC;

  • 2019-03-04 Victor Bocharsky

    Hey Saroj,

    We're glad you found this course interesting for you! ;)

    Cheers!

  • 2019-03-02 Saroj Shrestha

    Thanks for the course.

  • 2019-02-28 Victor Bocharsky

    Hey Tselkovskii,

    As I understand you did "composer update" but it didn't help? Hm, what PHP version do you have? And what command do you run when see this error?

    Btw, did you download the course code? I just double checked and it works for me, I don't see we have "security:check" in the code download from this course. As a workaround, I think you can remove "security:check" from your @auto-scripts, you can call it manually when needed.

    Cheers!

  • 2019-02-28 Tselkovskii

    Don't help remove and update

  • 2019-02-28 Tselkovskii

    Executing script security-checker security:check [KO]
    [KO]
    Script security-checker security:check returned with error code 255
    !!
    Script @auto-scripts was called via post-update-cmd

  • 2019-01-14 Emin

    Hey Vladimir Sadicov,

    Thank you it worked by updating the security-checker! :)

    Cheers!

  • 2019-01-11 Vladimir Sadicov

    Hey Emin

    It's coming from outdated "sensiolabs/security-checker" you have 2 options
    1) update security-checker
    edit your composer.json find "sensiolabs/security-checker": "^4.1" row and change it to "sensiolabs/security-checker": "^5.0" then run
    composer update sensiolabs/security-checker
    2) remove security-checker run
    composer remove sensiolabs/security-checker

    Cheers!

  • 2019-01-11 Emin

    Hey,

    When i added .env file to make it work i get the following error:


    Executing script security-checker security:check [KO]
    [KO]
    Script security-checker security:check returned with error code 1
    !!
    !! The web service did not return alerts count.
    !!
    !!
    Script @auto-scripts was called via post-install-cmd

    And i dont understand where this error is coming from? :(

    Or i can use the code from the last course to keep going

  • 2018-12-03 weaverryan

    Hey walter pothof!

    I'm glad you got it figured out and thanks for posting your solution! Keep going! :)

    Cheers!

  • 2018-12-03 weaverryan

    Dream come true! Thanks for the victory update! ❤️

  • 2018-12-02 walter pothof

    I have set the env var to export DATABASE_URL=mysql://root:YouPassword@127.0.0.1:3306/symfony4_space_bar
    It seems my root user allready had an password, so adjust YouPassword

  • 2018-12-02 walter pothof

    Hi,
    I downloaded the coursecode and did composer install

    When i try to create the database:php bin/console doctrine:database:create i get error:
    Access denied for user 'root'@'localhost' (using password: NO)

    This occures in the folowing 3 times files:
    In AbstractMySQLDriver.php line 113:
    In PDOConnection.php line 50:
    In PDOConnection.php line 46:

    I have tried to copy the env vars from the .env file
    export DATABASE_URL=mysql://root:@127.0.0.1:3306/symfony4_space_bar
    so they are on my computer aswell and i can see them when i do: printenv

    Can anyone tell me why i have no access to the database
    I cannot start the database now

    I have a local environment in wich i have other symfony projects running, so i know that is working correct.
    Both apache and mysql are also running as service on this computer.

  • 2018-12-02 CharlES

    Thank you for this awesome course. This weekend I was able to remove FosUserBundle and do it natively. Thank you.

  • 2018-11-20 Victor Bocharsky

    Hey Tomek,

    Thanks for sharing your ideas about solving this issue!

    Cheers!

  • 2018-11-19 Tomek Persona

    Problem:
    looks like its InnoDB + using utf8mb4 encoding, double check encoding.

    Solution (changing encoding not needed, so it will help you):
    For learnig just make column length a little shorter for Tag for example like length=160:
    1) check \src\migrations\ and search for file with ( $this->addSql('CREATE TABLE tag)...) change length to 160 for name & slug
    columns [for me it was \src\migrations\Version20180501142420.php]
    2) \scr\Entity\Tag for name & slug property change length=255 to length=160

    Hope it helps

  • 2018-10-02 Diego Aguiar

    Hmm, interesting. Did you re-create your database?

  • 2018-10-02 Mamunur Rashid

    Hey Diego Aguiar I am using xampp control version 3.2.2,
    I have changed the charset to utf8,
    still I am facing this problem, please help me?

  • 2018-10-01 Diego Aguiar

    Hey Mamunur Rashid

    Oh, that problem is related to the charset of your DB. Symfony4 by default set it to "utf8mb4" which uses more bytes per character (4 bytes), so what you can do is to upgrade your MySQL version to 5.7 or higher, or use a different charset, probably "utf8"

    Cheers!

  • 2018-10-01 Mamunur Rashid

    During the setup process, when I try to migrate database and table, I am acing the following problems,
    $ php bin/console doctrine:migrations:migrate

    Application Migrations

    WARNING! You are about to execute a database migration that could result in sche
    ma changes and data loss. Are you sure you wish to continue? (y/n)y
    Migrating up to 20180501143055 from 0

    ++ migrating 20180413174059

    -> CREATE TABLE article (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255)
    NOT NULL, slug VARCHAR(100) NOT NULL, content LONGTEXT DEFAULT NULL, published_
    at DATETIME DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE
    utf8mb4_unicode_ci ENGINE = InnoDB

    ++ migrated (0.11s)

    ++ migrating 20180413174154

    -> CREATE UNIQUE INDEX UNIQ_23A0E66989D9B62 ON article (slug)

    ++ migrated (0.03s)

    ++ migrating 20180414171443

    -> ALTER TABLE article ADD author VARCHAR(255) NOT NULL, ADD heart_count IN
    T NOT NULL, ADD image_filename VARCHAR(255) DEFAULT NULL

    ++ migrated (0.07s)

    ++ migrating 20180418130337

    -> ALTER TABLE article ADD created_at DATETIME DEFAULT NULL, ADD updated_at
    DATETIME DEFAULT NULL
    -> UPDATE article SET created_at = NOW(), updated_at = NOW()

    ++ migrated (0.06s)

    ++ migrating 20180418130730

    -> ALTER TABLE article CHANGE created_at created_at DATETIME NOT NULL, CHAN
    GE updated_at updated_at DATETIME NOT NULL

    ++ migrated (0.1s)

    ++ migrating 20180426184910

    -> CREATE TABLE comment (id INT AUTO_INCREMENT NOT NULL, author_name VARCHA
    R(255) NOT NULL, content LONGTEXT NOT NULL, created_at DATETIME NOT NULL, update
    d_at DATETIME NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE u
    tf8mb4_unicode_ci ENGINE = InnoDB

    ++ migrated (0.04s)

    ++ migrating 20180426185536

    -> ALTER TABLE comment ADD article_id INT NOT NULL
    -> ALTER TABLE comment ADD CONSTRAINT FK_9474526C7294869C FOREIGN KEY (arti
    cle_id) REFERENCES article (id)
    -> CREATE INDEX IDX_9474526C7294869C ON comment (article_id)

    ++ migrated (0.17s)

    ++ migrating 20180430194518

    -> ALTER TABLE comment ADD is_deleted TINYINT(1) NOT NULL

    ++ migrated (0.05s)

    ++ migrating 20180501142420

    -> CREATE TABLE tag (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT
    NULL, slug VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL, updated_at DATET
    IME NOT NULL, UNIQUE INDEX UNIQ_389B783989D9B62 (slug), PRIMARY KEY(id)) DEFAULT
    CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB
    Migration 20180501142420 failed during Execution. Error An exception occurred wh
    ile executing 'CREATE TABLE tag (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(25
    5) NOT NULL, slug VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL, updated_a
    t DATETIME NOT NULL, UNIQUE INDEX UNIQ_389B783989D9B62 (slug), PRIMARY KEY(id))
    DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB':

    SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too lo
    ng; max key length is 767 bytes

    In AbstractMySQLDriver.php line 126:

    An exception occurred while executing 'CREATE TABLE tag (id INT AUTO_INCREM
    ENT NOT NULL, name VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, creat
    ed_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, UNIQUE INDEX UNIQ_38
    9B783989D9B62 (slug), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLAT
    E utf8mb4_unicode_ci ENGINE = InnoDB':

    SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was t
    oo long; max key length is 767 bytes

    In PDOConnection.php line 109:

    SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was t
    oo long; max key length is 767 bytes

    In PDOConnection.php line 107:

    SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was t
    oo long; max key length is 767 bytes

    doctrine:migrations:migrate [--write-sql [WRITE-SQL]] [--dry-run] [--query-time]
    [--allow-no-migration] [--configuration [CONFIGURATION]] [--db-configuration [D
    B-CONFIGURATION]] [--db DB] [--em EM] [--shard SHARD] [-h|--help] [-q|--quiet] [
    -v|vv|vvv|--verbose] [-V|--version] [--ansi] [--no-ansi] [-n|--no-interaction] [
    -e|--env ENV] [--no-debug] [--] <command> [<version>]

  • 2018-09-26 Victor Bocharsky

    Hey Kim,

    Thanks for sharing your solution! I think you can continue using MySQL 5.5 but you need to specify your version in the doctrine configuration, see this "server_version" key: https://github.com/symfony/...

    Cheers!

  • 2018-09-26 Kim

    I was getting this (https://stackoverflow.com/q... error with mysqlserver versions 5.5 and 5.6, switching to 5.7 solved this.

  • 2018-09-17 Victor Bocharsky

    Hey Vlad,

    Not sure :) Didn't that example fit you?

    Cheers!

  • 2018-09-15 Vlad

    Any other options?

  • 2018-09-14 Victor Bocharsky

    Hey Vlad,

    Here's how simulate HTTP authentication in a functional test, see docs: https://symfony.com/doc/cur...

    Cheers!

  • 2018-09-13 Vlad

    How do I simulate user authentication in order to test authenticated actions using PHPUnit? I know how to do that for JWT authentication, but not regular user authentication. Please help!

  • 2018-09-11 Victor Bocharsky

    Hey Direktorius,

    We're going to release a new video every day! Working on it right now.

    Cheers!

  • 2018-09-11 Direktorius

    Very excited for new tutorials.

    When can we expect the followup videos?

  • 2018-09-10 weaverryan

    Hey Stéphane!

    Ah, thanks for noting that! I had a little "pointer" on the wrong commit for generating the "start" code for this tutorial. I just fixed that and it should be reflected in just a few minutes. The start code you downloaded will also be missing a few CSS files and some fixtures changes. All will be better now. Sorry about that - but thanks again for pointing it out!

    Cheers!

  • 2018-09-10 Stéphane

    Hello
    Thank for this new tuto.
    When I install the application with start folder, the version of Sf4 is 4.0.14 not 4.1 like you say in video ? It's normal ?

  • 2018-09-10 weaverryan

    Yo Peter Kosak!

    > Will we touch account lockout. Example if someone/bot will try to submit 1000 times your login form the account should be locked for 15 minutes. After 3 incorrect passwords, another 3 would be 30mins then for a day so is there any easy pre-build function for this?

    This was NOT planned. But, it's interesting. Mostly, this would probably be accomplished by adding a few extra fields to your User entity so you could track number of failed attempts, and when the last attempt was. Then, probably inside a checkCredentials() method that we'll learn about in a few days, I'd check that and fail if you have one of those conditions. For the "submit the login form 1000" times, that's a bit harder. You could store a counter in the session very easily - but probably the "attacker" is using a programmatic client with no session. So, you'd probably need a new table to tracks logins by "IP".

    Anyways, when you learn about the "authenticator", you'll see that it's very easy to add custom logic and add "exit" points.

    > Second question is more general but I was thinking about it when I saw lesson 3 of this course. JSON field.

    VERY good question. It comes down to the complexity of your role system. For example, here on KnpUniversity, we have 2 roles: ROLE_USER and ROLE_ADMIN. And, there are 4 of us with ROLE_ADMIN. It's really simple, so the roles field works GREAT. But, you're right that if you want to have a lot of control over roles - listing who has what role, even adding a description in the database for what a role does - then a relationship setup will be better. This is a very valid point. It's not a matter of performance - just choosing how much complexity your app needs.

    If you *did* want this setup, then yea, you could have a `Role` entity or even a Group entity that has an json array of roles (or a Group entity that is ManyToMany to a Role entity) and then a User can be ManyToMany to Group. You can see that this can scale up to a lot of different levels of complexity. The cool thing is that Symfony only cares that your User has a getRoles() method that returns a string of roles. If you have a really complex setup, your getRoles() method would ultimately just be looping over its related Role entity objects (or looping over its related Group objects... and *their* Role objects) to create this array of strings. Performance is not really a problem because getRoles() is only called on login and then stored in the session (though, there is a pull request - https://github.com/symfony/... - to change this behavior).

    I hope that clarifies a bit. Great question! :D

    Cheers!

  • 2018-09-10 Peter Kosak

    Ryan, first of all thank you soooo much for this course I've been waiting for like the other one regarding forms.

    Anyway I have 2 questions that comes to my mind.

    First one:
    Will we touch account lockout. Example if someone/bot will try to submit 1000 times your login form the account should be locked for 15 minutes. After 3 incorrect passwords, another 3 would be 30mins then for a day so is there any easy pre-build function for this?

    Second question is more general but I was thinking about it when I saw lesson 3 of this course. JSON field.
    This is soo powerfull field these days that I think will drive people into wrong database schema setup in the future. (instead of creating linked tables for manytomanythey will store it in one entity) I am will be probably one of them. We/You are going to be storing roles in DB as JSON value. Before json we would have ManyToMany reletionship between roles and user. So the question is; is it actually good practise to store them as a JSON. What if I want to list all the users where ROLE is "admin" and not "user"? Why we/you/symfony is not using this elsewhere for ManyToMany relationship but usually we have to create this manytomany relationship (2 entities)? Whats the difference between following relationships Student & Course vs User & Roles (why we dont use JSON to store the courses in student class?).

    Is it just inconsistency or does it have some logical answer? I think only the performace will be the answer so the next question is: is it actually better approach to store Roles in separate table/entity?