gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
If your login system looks similar to the traditional email & password or username & password setup, Symfony has a nice, built-in authentication mechanism to help. In config/packages/security.yaml
, under the main
firewall, add a new key: json_login
. Below that, set check_path
to app_login
.
security: | |
... lines 2 - 12 | |
firewalls: | |
... lines 14 - 16 | |
main: | |
... lines 18 - 19 | |
json_login: | |
check_path: app_login | |
... lines 22 - 36 |
This is the name of a route that we're going to create in a second - and we'll set its URL to /login
. Below this, set username_path
to email
- because that's what we'll use to log in, and password_path
set to password
.
security: | |
... lines 2 - 12 | |
firewalls: | |
... lines 14 - 16 | |
main: | |
... lines 18 - 19 | |
json_login: | |
... line 21 | |
username_path: email | |
password_path: password | |
... lines 24 - 36 |
With this setup, when we send a POST
request to /login
, the json_login
authenticator will automatically start running, look for JSON in the request, decode it, and use the email
and password
keys inside to log us in.
How does it know to load the user from the database... and which field to use for that query? The answer is: the providers
section. This was added in the last tutorial for us by the make:user
command. It tells the security system that our User
lives in Doctrine and it should query for the user via the email
property. If you have a more complex query... or you need to load users from somewhere totally different, you'll need to create a custom user provider or an entirely custom Guard authenticator, instead of using json_login
. Basically, json_login
works great if you fit into this system. If not, you can throw it in the trash and create your own authenticator.
So, there may be some differences between your setup and what we have here. But the really important part - what we're going to do on authentication success and failure - will probably be the same.
To get the json_login
system fully working, we need to create that app_login
route. In src/Controller
create a new PHP class called, how about, SecurityController
. Make it extend the normal AbstractController
and then create public function login()
. Above that, I'll put the @Route
annotation and hit tab to auto-complete that and add the use
statement. Set the URL to /login
, then name="app_login"
and also methods={"POST"}
: nobody needs to make a GET request to this.
... lines 1 - 4 | |
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | |
... line 6 | |
use Symfony\Component\Routing\Annotation\Route; | |
... line 8 | |
class SecurityController extends AbstractController | |
{ | |
/** | |
* @Route("/login", name="app_login", methods={"POST"}) | |
*/ | |
public function login() | |
{ | |
... lines 16 - 18 | |
} | |
} |
Initially, you need to have this route here just because that's the way Symfony works: you can't POST to /login
and have the json_login
authenticator do its magic unless you at least have a route. If you don't have a route, the request will 404 before json_login
can get started.
But also, by default, after we log in successfully, json_login
does... nothing! I mean, it will authenticate us, but then it will allow the request to continue and hit our controller. So the easiest way to control what data we return after a successful authentication is to return something from this controller!
But... hmm... I don't really know what we should return yet - I haven't thought about what might be useful. For now, let's return $this->json()
with an array, and a user
key set to either the authenticated user's id or null.
... lines 1 - 13 | |
public function login() | |
{ | |
return $this->json([ | |
'user' => $this->getUser() ? $this->getUser()->getId() : null] | |
); | |
} | |
... lines 20 - 21 |
Let's try this! When we go to https://localhost:8000
, we see a small frontend built with Vue.js. Don't worry, you don't need to know Vue.js - I just wanted to use something a bit more realistic. This login form comes from assets/js/components/LoginForm.vue
.
It's mostly HTML: the only real functionality is that, when we submit the form, it won't actually submit. Instead, Vue will call the handleSubmit()
function. Inside, uncomment that big axios block. Axios is a really nice utility for making AJAX requests. This will make a POST request to /login
and send up two fields of data email
and password
. this.email
and this.password
will be whatever the user entered into those boxes.
... lines 1 - 23 | |
<script> | |
... lines 25 - 41 | |
axios | |
.post('/login', { | |
email: this.email, | |
password: this.password | |
}) | |
.then(response => { | |
console.log(response.data); | |
//this.$emit('user-authenticated', userUri); | |
//this.email = ''; | |
//this.password = ''; | |
}).catch(error => { | |
console.log(error.response.data); | |
}).finally(() => { | |
this.isLoading = false; | |
}) | |
... lines 58 - 60 | |
</script> | |
... lines 62 - 65 |
One important detail about axios is that it will automatically encode these two fields as JSON. A lot of AJAX libraries do not do this... and it'll make a big difference. More on that later.
Anyways, on success, I'm logging the data from the response and on error - that's .catch()
- I'm doing the same thing.
Since we haven't even tried to add real users to the database yet... let's see what failure feels like! Log in as quesolover@example.com
, any password and... huh... nothing happens?
Hmm, if you get this, first check that Webpack Encore is running in the background: otherwise you might still be executing the old, commented-out JavaScript. Mine is running. I'll do a force refresh - I think my browser is messing with me! Let's try that again: queso_lover@example.com
, password foo
and... yes! We get a 401 status code and it logged error
Invalid credentials.
. If you look at the response itself... on failure, the json_login
system gives us this simple, but perfectly useful API response.
Next, let's hook up our frontend to use this and learn how json_login
behaves when we accidentally send it a, let's say, less-well-formed login request.
// composer.json
{
"require": {
"php": "^7.1.3, <8.0",
"ext-ctype": "*",
"ext-iconv": "*",
"api-platform/core": "^2.1", // v2.4.5
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/annotations": "^1.0", // 1.13.2
"doctrine/doctrine-bundle": "^1.6", // 1.11.2
"doctrine/doctrine-migrations-bundle": "^2.0", // v2.0.0
"doctrine/orm": "^2.4.5", // v2.7.2
"nelmio/cors-bundle": "^1.5", // 1.5.6
"nesbot/carbon": "^2.17", // 2.21.3
"phpdocumentor/reflection-docblock": "^3.0 || ^4.0", // 4.3.1
"symfony/asset": "4.3.*", // v4.3.2
"symfony/console": "4.3.*", // v4.3.2
"symfony/dotenv": "4.3.*", // v4.3.2
"symfony/expression-language": "4.3.*", // v4.3.2
"symfony/flex": "^1.1", // v1.18.7
"symfony/framework-bundle": "4.3.*", // v4.3.2
"symfony/http-client": "4.3.*", // v4.3.3
"symfony/monolog-bundle": "^3.4", // v3.4.0
"symfony/security-bundle": "4.3.*", // v4.3.2
"symfony/twig-bundle": "4.3.*", // v4.3.2
"symfony/validator": "4.3.*", // v4.3.2
"symfony/webpack-encore-bundle": "^1.6", // v1.6.2
"symfony/yaml": "4.3.*" // v4.3.2
},
"require-dev": {
"hautelook/alice-bundle": "^2.5", // 2.7.3
"symfony/browser-kit": "4.3.*", // v4.3.3
"symfony/css-selector": "4.3.*", // v4.3.3
"symfony/maker-bundle": "^1.11", // v1.12.0
"symfony/phpunit-bridge": "^4.3", // v4.3.3
"symfony/stopwatch": "4.3.*", // v4.3.2
"symfony/web-profiler-bundle": "4.3.*" // v4.3.2
}
}