We use cookies on this site to enhance your user experience. By clicking "OK, I Agree" or using our site, you consent to the use of cookies unless you have disabled them.
View our cookie policy to learn more.

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.

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.

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.

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.

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.

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 [email protected], 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: [email protected], password foo and... yes! We get a 401 status code and it logged errorInvalid 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.

Leave a comment!

2020-05-14Patrick Purcell

Anton Kolenkov - You're selling us a bit short. We have this working in production on multiple sites. Also have it working between different domains and Ionic smartphone clients. How far along are you? Any specific errors? Be sure you're using "withCredentials: true" client-side and you're using some type of CORS Bundle on the Symfony4 side. Cheers!

Fortunately, you're not the first person to ask this :D. The short answer is that remember_me does not work with json_login. But if you re-implement json_login as a Guard authenticator (which is not too difficult), you *can* make it support that. Check out the thread for answers here - https://symfonycasts.com/sc... - and let me know if you have some luck.

Cheers!

2020-04-19Anton Kolenkov

Hi! Is it possible to implement "remember me" functionality with json_login? My front and back are living on different subdomains because their development is completely independent. Domains are my.example.com and api.example.com, if it helps. Api is used to fuel SPA and it is somewhat inconvenient to relogin each time session ends. I've found that json_login doesn't support "remember me" and according to this thread, never will(but why support remember_me field then?). Solutions I've found over internet(see below) are either incomplete or not working, even if it's claimed as working for Symfony4:https://www.purcellyoon.com...https://stackoverflow.com/q...It seems guys from FOSUserBundle somehow implemented it: https://github.com/FriendsO...But I cannot get whole picture and make it work. Documentation about working with TokenBasedRememberMeServices(for example) is very poor(next to non-existent).Can you shed some light, please, and give me some directions? IDK, maybe I missed some documentation, after all..Some basic information from bin/console about:

That's a really excellent question - and not something I've had to implement before! So, if your backend is hosted on another server, are you still using session-based authentication - i.e. the other server sets a cookie and your JavaScript sends it to be authenticated? If so, indeed - that complicates things :). First, let me say that this stuff is *tricky*, so I'll do my best to be accurate here - but I can't make any promises.

Because of your setup, the session cookie that's set by the other server doesn't use SameSite... which means that it *is* in theory vulnerable to CSRF attacks. From some looking around, it looks like you could implement CSRF by using headers or by using cookies that only your Angular domain can use - https://security.stackexcha...

The topic in general doesn't really relate to API Platform - it's more of a Symfony security setup. This bundle - https://github.com/dunglas/... - which doesn't work for Symfony 5 (as the author is mostly hoping that SameSite cookies will eliminate the need for CSRF in the long-run), is a good example of how this might all work.

A more abstract answer to your question is this: using session/cookie based authentication is GREAT when your frontend and backend live on the same domain. By using SameSite cookies, you *may* not need to worry about CSRF (most of your users will be using browsers that support SameSite). If you have your frontend and backend on different domains, security is just tricky. You probably don't want to use session-based authentication (using token-based instead), but now you have the problem of storing authentication tokens in JavaScript, which can be dangerous. And this leads to another solution: put a backend on the same domain as your frontend... which makes the API requests for you. This is actually what GitHub does. The JavaScript on GitHub.com does *not* talk to the GitHub API directly, as far as I've seen. Nope, it makes AJAX requests back to GitHub.com and uses session-based authentication. Behind the scenes. the GitHub.com backend probably makes API requests to the GitHub API using API tokens.

I hope this helps - but sorry if it does not ;). I could be wrong about some of these details - but I've spent a lot of time researching them.

Cheers!

2020-03-17skywaler

Hi! I have an angular frontend aplication with is hosted on other server, so there is no way to read cookies set to httpOnly: true. As far as i understand CSRF is not available then? Or I could do some configuration in API platform to anable this? Thanks in advance.

2020-02-18Simon Denman

Thanks,In the end, I compromised by going back to JWT but setting the token in an http-only cookie.This gives me greater flexibility for the future, seems more secure and has the advantage of being a genuinely RESTful solution.

2020-02-18Diego Aguiar

Ohh, I see your problem. I found this article that explains how you can send your credentials via CORS request http://promincproductions.c...I'm not sure about how secure it is, it's possible that's not the most secure option but it should work. If you don't like it, or you are aware of any vulnerabilities, then, using JWT would be your option

Cheers!

2020-02-18Simon Denman

Actually, I think my problem is that I have separated the front and back-end (as per API-Platform recommendations) so that they're currently on different ports of localhost - my thinking being that in production I can have them on different servers or even different domains.However, according to https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch:fetch() won't receive cross-site cookies; you can’t establish a cross site session using fetch. Set-Cookie headers from other sites are silently ignored.

So I have a new question for you and Ryan: Do you still consider session cookies to be an acceptable means of authentication for APIs when the front and back ends are separated like this?

The folks at API-Platform seem pretty adamant that JWT is the only way to go for APIs.

I'm not sure how you are doing your API calls but seems to me that you are not sending the session cookie. We explain how in this chapter https://symfonycasts.com/sc...

Cheers!

2020-02-16Simon Denman

Hi, have just come back to this after rebuilding my backend with the latest version of api-platform etc and decided to use session-based authentication instead of JWT as it looks like the cleaner solution for my needs.

BUT - although the modified login stage seems to work ok according to the logs: [info] User has been authenticated successfully. [debug] Stored the security token in the session.

It continues to authenticate anonymously when my client subsequently fetches resources from the API: [info] Populated the TokenStorage with an anonymous Token.

As far as I understand it, absolutely :). The JWT that you configure with MecureBundle will be used to communicate and authenticate against your Mecure *hub*, *not* your Symfony app. Basically, when you set up the Mercure Hub, you configure some secret key (not a JWT) on the JWT_KEY setting. You then *use* that key to create JWT's that will be used by MecureBundle / your app. Basically, you'll create a JWT that has this structure (https://jwt.io/#debugger-io... and then *sign* it with the same "key" that the Mecure hub is using (see https://github.com/dunglas/... and https://symfony.com/doc/cur...

I hope that helps. The key this is that your Symfony security and this whole "JWT Mecure" thing have nothing to do with each other :). The Mercure thing is all about communicating securely between your Symfony app and the Mercure hub.

Cheers!

2019-10-05Taieb Baccouche

HiThank you for this great tutorial. Is it possible to implement the json_login authentication with Mercure knowing that API Platform doc https://api-platform.com/do..., they are using JWT. My main goal is to secure the API and notify users when a new article is created. Thanks

2019-10-04zpine

Hi

Those who trying to implement the json_login authentication instead of JWT with separate domain Single Page App's (html static page on a separate domain without php). You may get bunch of different CORS warnings and errors during the login process. I have spent several hours to resolve this issue and I believe it may useful to someone...

> First, it seems like you can still see /api page and run commands in the page without any authentication. How can I make /api page require login?

If you'd like to do this, use access_control in security.yaml to require, for example, ROLE_USER or ROLE_ADMIN. Basically, you can use normal Symfony security to protect this if you'd like.

> Second, do you have plan to add JWT authentication in API Platform in this tutorial?

No :). In a future tutorial, we're going to talk about some different authentication schemes beyond the one we show here. That's on purpose - I think JWT is being over-used - its truly useful if you have a central authentication system and then many different applications where you want an API client to be able to talk to all of these with the same "token" and avoid all of these separate apps from needing to communicate back to the central authentication system on every request. It's a situation that few people actually have. For many use-cases, you should be using HttpOnly cookie-based authentication... which could be a session cookie or JWT. Though, while this is a fine use of JWT, it doesn't really offer any advantages over session-based auth in most situations and is more complex to setup.

That's why we won't talk about it in this tutorial... but I do want to expand on all of this in a future tutorial. Let me know if that helps :).

Cheers!

2019-08-22Sung Lee

Thanks for the tutorials! I have two questions :)First, it seems like you can still see /api page and run commands in the page without any authentication. How can I make /api page require login?Second, do you have plan to add JWT authentication in API Platform in this tutorial? I tried the API Platform website, but it only shows how to install and configure. There are no further information to get started. Also Symfony RESTful API: Authentication with JWT (Course 4) doesn't seem much relevant to the API Platform. Thanks for your work!

2019-08-15Victor Bocharsky

Hey Sasa,

All popular JS frameworks have both pros and cons, and most of the time the choice between of them falls on one that you know the best :) Really, if know a JS framework and you're comfortable with it - just use it. Well, sometimes it's more complex choice because your choice may depends on your project and its needs. But what about working with API - I think all popular JS frameworks are good in it, that's a pretty simple thing and it's more matter of taste.

Well, if you're interested about our opinion - we love ReactJS, and we have a screencast about it: https://symfonycasts.com/sc... . Also, we're looking at VueJS, so probably the further screencasts might be based on it, but it's not 100% yet :)

I hope this helps!

Cheers!

2019-08-09Sasa Milivojevic

Hi, I'm very happy about this course.

I'm wondering if there is any recommendation which frontend framevork is best (easiest) to use with the Api platform.

And I wonder if one particular frontend framevork will be used in the following courses and which one?