Chapters
33 Chapters
|
3:34:13
|
Login to bookmark this video
-
Course Code
Subscribe to download the code!Compatible PHP versions: ^7.1.3
Subscribe to download the code!Compatible PHP versions: ^7.1.3
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
06.
Login Form Authenticator
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Subscribe to jump to this part in the video!
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": "^7.1.3",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/annotations": "^1.0", // 1.10.2
"doctrine/doctrine-bundle": "^1.6.10", // 1.10.2
"doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // v2.0.0
"doctrine/orm": "^2.5.11", // v2.7.2
"knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
"knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
"knplabs/knp-time-bundle": "^1.8", // 1.8.0
"nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
"php-http/guzzle6-adapter": "^1.1", // v1.1.1
"phpdocumentor/reflection-docblock": "^3.0|^4.0", // 4.3.0
"sensio/framework-extra-bundle": "^5.1", // v5.2.0
"stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
"symfony/asset": "^4.0", // v4.1.4
"symfony/cache": "^3.3|^4.0", // v4.1.4
"symfony/console": "^4.0", // v4.1.4
"symfony/flex": "^1.0", // v1.21.6
"symfony/framework-bundle": "^4.0", // v4.1.4
"symfony/lts": "^4@dev", // dev-master
"symfony/property-access": "^3.3|^4.0", // v4.1.4
"symfony/property-info": "^3.3|^4.0", // v4.1.4
"symfony/security-bundle": "^4.0", // v4.1.4
"symfony/serializer": "^3.3|^4.0", // v4.1.4
"symfony/twig-bundle": "^4.0", // v4.1.4
"symfony/web-server-bundle": "^4.0", // v4.1.4
"symfony/yaml": "^4.0", // v4.1.4
"twig/extensions": "^1.5" // v1.5.2
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
"easycorp/easy-log-handler": "^1.0.2", // v1.0.7
"fzaninotto/faker": "^1.7", // v1.8.0
"symfony/debug-bundle": "^3.3|^4.0", // v4.1.4
"symfony/dotenv": "^4.0", // v4.1.4
"symfony/maker-bundle": "^1.0", // v1.7.0
"symfony/monolog-bundle": "^3.0", // v3.3.0
"symfony/phpunit-bridge": "^3.3|^4.0", // v4.1.4
"symfony/stopwatch": "^3.3|^4.0", // v4.1.4
"symfony/var-dumper": "^3.3|^4.0", // v4.1.4
"symfony/web-profiler-bundle": "^3.3|^4.0" // v4.1.4
}
}
87 Comments
Heyi I am trying to add some validation to login form.
I have followings:
class LoginType extends AbstractType
{
}
Cannot check if an unsubmitted form is valid. Call Form::isSubmitted() before Form::isValid().
`How can we achieve the validation and the validation errors in a form authenticator?
I have ended up with the following: FYI:
Hey Yahya E.!
Ah! Your second solution works. But you first solution was SO close. The change that got you was something that changed from Symfony 3 to Symfony 4. Here is what you needed in the first code:
That's it! The key is the
$form->isSubmitted(). That line doesn't make as much sense in this context, but basically, Symfony wants you to also make sure the form was submitted (i.e. that this wasn't a GET request) before actually asking if it's valid.I hope it will help simplify your code a bit!
Cheers!
Does it has any security benefits to use SF form over our standard html form, which would make the more work worth? (e.g. better protection against xss or mysql injection?)
Hey Mike,
Unfortunately, any forms do not protect you from MySQL injection, your MySQL client should worry about it. And Doctrine do so if you use it the correct way, i.e. use parameters instead of concatenating dynamic values to query strings. You can check our Doctrine tutorial about how to write good queries: https://symfonycasts.com/sc... .
What about XSS - yes! Symfony Forms has CSRF protection enabled by default. You can turn it of in global settings or turn off in specific forms. To ensure you have CSRF protection in Symfony Forms - you can render the form and look for hidden field with token.
And fairly speaking, even if we're talking about simple forms, it's more convenient to work with Symfony Forms instead of handle them manually. Well, you can do an experiment, implement a form with Symfony Form and then do the same with custom form and compare results.
Cheers!
Any explanation why start() functioon is fired even that support return false, normally the authenticator should be skipped ?
Hello ahmedbhs
start()method is a part ofAuthenticationEntryPointInterfaceit helps to define which way user should be authenticated if you have multiple authenticators. You can read more about it here https://symfony.com/doc/current/security/entry_point.htmlCheers!
I am new to Symfony web application development. And at the moment Iam building a web application to get my head around Symfony web development. I have stored some sensitive keys in the Symfony's secrets management system (vault). And I want to access a key in the vault to do some operation. I need to access the vault items in the LoginFormAuthenticator class which extends AbstractFormLoginAuthenticator, and $this->getParameter('key_name') is not working in the LoginFormAuthenticator class.
Is there a work around where I should be able to access the vault items in that class ?
Hey Chamal,
To access an environment variable in PHP user $_ENV or $_SERVER vars, i.e. you can get that "KEY_NAME" env var via $_ENV['KEY_NAME']
or $_SERVER['KEY_NAME']. Usually, env vars are written in uppercase.
Or, if you want to get it via getParameter() instead - you need to create the "parameter" first. For example, in config/services.yaml add:
And then in your PHP code call "->getParameter('key_name')" that you just created.
I hope this helps!
Cheers!
Hi Victor,
Thanks for your reply.
That is what I have exactly done it, in my code. Pasted the code below for more clarity, along with the error message I received.
in config/services.yaml
In LoginFormAuthenticator
And I get the following exception:
<blockquote>Attempted to call an undefined method named "getParameter" of class "App\Security\LoginFormAuthenticator".
</blockquote>
Hey Chamal,
Ah, I see your problem now :) Sure, the "getParameter()" method is available in your controllers only, and only in case you extends Symfony base controller. If you want to get a parameter in your service - you have to inject it there, and that's a good practice btw!
So, the easiest way will be next: in your config/services.yaml, create a bind for a variable name and associated parameter:
And then in your "LoginFormAuthenticator" add the constructor if you don't have it, that will look like this:
I.e., now you can ask for $dbAccessKey parameter injection in any service this way. The same way do for other parameters you need to inject.
Another way would be to inject the Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface service into the LoginFormAuthenticator that will give you access to all the parameters via ->get('db_access_key'). It might be useful when you need to get access to many parameters in your service. But unless you need just one or a few - the 1st way is recommended.
I hope this helps!
Cheers!
Hi Victor,
Thanks for your replying.
I have already solved my issue, and it was the solution that you have suggested second. I have injected the ContainerBagInterface to the LoginFormAuthenticator class, and it worked. I have tried your first solution as well, but it didnt work for me. Maybe because I have configured it incorrectly. I will try out again.
Thank you very much again for helping to resolve my issue.
Cheers !
Hey Chamal,
Thanks for letting me know the 2nd solution works for you. Yeah, it may also depends on the version of Symfony you're using in your project. Anyway, if the 2nd works for you well - great!
Cheers!
Hello there !
UserProvider is passed to getUser() but not used in your example.
Just wondering in what cases UserProvider would be useful in getUser().
Thanks !
Hey Nicolas-S!
Wonderful question! I have an answer for it back here - https://symfonycasts.com/sc... - let me know if it helps :).
Cheers!
Perfect, thanks !
Hey! In recent months, there has been a new way to manage authentication with Symfony: https://symfony.com/doc/cur...
It uses the new generation of GUARD if I understood correctly. But that's not all, by setting up this new system, we could use the "new" component of Symfony: The login throttling ( https://symfony.com/blog/ne... ) based on the RateLimiter.
I went through the doc 'to try to set up the new authentication system, based on our LoginFormAuthenticator, but it is much more complicated than expected, the doc is not very clear and goes in all meaning. In the end, we don't really know what we should use, which class to extend or implement, which function to perform and how. This is a drawback ...
Have you planned to make an Update video about this new component?
(And why not create a new command to set up this new authentication mode, like the php bin/console make:auth command)
Hey @kieuga!
Yes! I know and love this system - I’ve been working with Wouter... who has done ALL the real work on it - to perfect it.
A few things:
1) we will do a tutorial, but I’m waiting for symfony 5.3, as a few more (nice) changes are coming that I want to show (the splitting up of UserInterface and renaming of getUsername).
2) version 1.26.0 of maker bundle - https://github.com/symfony/... - was updated to support the new system :). It detects if you have it enabled and generates different code. We would like to add more options to that command, but it supports the current empty and form login authenticators.
If you have any questions about the new system before 5.3, I’d be happy to answer them :).
Cheers!
It's just awesome weaverryan Good job! For my part, I continued to reform this new authentication system (but very good news that the maker will be able to manage this soon!). I finally succeeded.
On the other hand, I don't know if you will talk about it in your tutorial or even if the maker will handle it, but regarding the Login Throttling, it does not work at all for me. Finally, I think this is mostly because I'm on Windows, and Semaphore is not supported for the locker. So I opted for "Flock" for the locker by simply replacing
LOCK_DSN=semaphorebyLOCK_DSN=flockin my <b>.env</b>, but it's like it's being ignored, like if I had not implemented this new feature. Weird. Hope your tutorial will talk about its use for us recalcitrant Windows lovers! :DHey Kiuega!
Woohoo - nice work!
Hmm. Indeed, I will have to keep this in mind. When you use the
login_throttlingunder your firewall, it crates a basic rate limiting service to use. But there is also an option to use a custom, rate limiter service id.So here is what I bet is happening and what you should do:
1) Configure a custom rate limiter - via the framework.rate_limiter config to use Flock. It sounds like you've already done this. If you named it "login_throttler" in the config file, then behind the scenes, this is creating a service with the id "limiter. login_throttler".
2) Specify this in your security config:
I'm doing some guess work here. There are some holes in the docs (lock is documented, rate limiter is documented and login throttling is documented, but this crosses all 3).
Btw, if you want to do any debugging, here is the class that's called on login and does the rate limiting logic: https://github.com/symfony/symfony/blob/5.x/src/Symfony/Component/Security/Http/EventListener/LoginThrottlingListener.php
Let me know what you find out :).
Cheers!
Grrr my previous answer was marked as spam. I am enraged at having to start my answer all over again. :x
Hey weaverryan and thanks for your answer !
So I followed your directions, and using the doc ', I configured everything as needed.
I kept in the <b>.env</b> my
LOCK_DSN=flockto be able to use flock all the time without reconfiguring it each time.I created my custom <b>rate_limiter</b>:
<blockquote>
Autowirable Types
=================
The following classes & interfaces can be used as type-hints when autowiring:
(only showing classes/interfaces matching limiter)
Symfony\Component\RateLimiter\RateLimiterFactory $loginThrottlerLimiter (limiter.login_throttler)
</blockquote>
In fact, I can still try as many times as I want with the wrong password. However, if I try with a good password this time around I get this:
<blockquote>
Argument 2 passed to Symfony\Component\Security\Http\EventListener\LoginThrottlingListener::__construct() must be an instance of Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface, instance of Symfony\Component\RateLimiter\RateLimiterFactory given, called in C:\Users\user\Desktop\Sylvia\var\cache\dev\ContainerWv8DSJy\getSecurity_Listener_LoginThrottling_MainService.php on line 38
</blockquote>
I don't understand, the documentation tells us that we can use a custom rate_limiter for this feature, but doesn't seem to support it. Or am I doing something wrong?
Hey Kiuega!
First, apologies for my slow reply - it was crazy week :p.
Really sorry about that :/. We DO find and rescue these... not that this helps you for this time :/
Ok, let's get to your question! There are 2 interesting pieces. And let me first say that you followed my instructions beautifully. But... I apparently did not lead you down the path all the way correctly ;).
I've just asked the author of this feature about this. There is a little disconnect that I didn't realize. Basically, the
framework.rate_limiterconfig allows you to create RateLimiterFactory... but what the login throttling system needs is a RequestRateLimiterInterface... which is kind of a class that wraps the actual "rate limiters". Right now, I don't see a simple way of actually creating this. I mean, it's possible, but much harder than it should be. I'm either missing a detail (very possible), or this was an oversight. That's why I've asked the author. I will let you know :).If you want to create the service yourself, create a new service in services.yaml that looks like this:
Then pass this
app.default_login_rate_limiteras your limiter in security.yaml.This part... I can't explain. Basically, after you followed my directions, your rate limiter was totally misconfigured. This means that you should have gotten an error regardless of whether your typed in your password correctly or incorrectly: the rate limiter should have been used in both cases.
When I tried this locally (just now), I DID get the error regardless of whether I typed in the right password or wrong password. So something in your setup is different. I'm using the generic login form that you get with make:auth from MakerBundle. What does your authenticator look like?
Cheers!
Hi Ryan! No worries I understand that you are very busy, thank you for taking time for me it's nice!
Okay so small change compared to the last time at my place. I installed the dual-boot to be able to develop on Ubuntu (yeeees awesome) and logically be able to use semaphore naturally.
<blockquote>When I tried this locally (just now), I DID get the error regardless of whether I typed in the right password or wrong password. So something in your setup is different. I'm using the generic login form that you get with make:auth from MakerBundle. </blockquote>
The answer below (the one crossed out) is totally wrong, ignore it. The real answer is just below what is crossed out.
<s>What you said there disturbed me. If in doubt, I also recreated a new project (EDIT: no I took an old project which was still virgin, and the rest of this text is therefore incorrect. I'm editing, I edit this post as soon as I'm done), and I set up the authentication with the make: auth command (so by generating a guard authentication, that is to say, the future old version), and I configured a login_throttler in the security.yaml, in the most basic way.
Well even logging in more than 2 times (the maximum number I allowed) with bad credentials, I got no other error than a simple "invalid credentials". And I was able to log in quite normally afterwards.
I took the trouble to post the project on github: https://github.com/bastien70/symfony-login-throttler-test
On my main project, this is the new authenticator that I put in place and which brings me exactly the same result.</s>
I will provide more details to my answer by editing it with new elements later, the time to test everything you have offered me!
EDIT : OKAY ! I came to understand some things.
If we create a new project, and generate the authenticator directly with
`make:auth
`, it is based on the old version of the authenticator.
If we create a project, and first modify the security.yaml to tell it to use the new way to authenticate (with
enable_authenticator_manager: true), then generate an authenticator with the`make:auth
`command, so this time it generates the new version of the authenticator (and indeed there are differences with the file that I had tried to create on my own project, in particular at the level of the Passport object in the authenticate ()).
And the <b>login_throttler</b> seems to work on it!
Here is <u>a working github repository:</u> https://github.com/bastien70/symfony-working-login-throttler
EDIT2 : I just added an issue about this new authenticator system about @Security() and @IsGranted() annotations : https://github.com/symfony/symfony/issues/40571
Hey Kiuega!
Wow! Awesome job there! Yes, you're 1000% right about make:auth... and I'm so happy that you got the login throttler working! And what a *beautiful* issue you made on Symfony - it was able to quickly look into your reproducer and understand what was going on! Though... I still can't quite spot what's wrong with @Security from looking at its code in the bundle. Anyways, use @IsGranted()... it's better anyways (but we should fix this).
Cheers!
Hello here !
Thank you for your participation in the Github issue!
You said : <blockquote>In SensioFrameworkExtraBundle, I think we should deprecate
@Security.I created@IsGrantedto be easier to use... but also because I thought the expression is not a good place to have that logic.</blockquote>Indeed it would be better to try to solve the problem instead. According to the documentation ( https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/security.html ), the
@Securityis still very useful for expressions (but if I understand your answer correctly, you would prefer that the expressions be finally moved to custom voters?), or for accessing the request and checking something (in my case I created a voter which verifies that the request was sent with Ajax, and using the @Security annotation allows me to directly access the request without bothering me, as explained in the doc https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/security.html#security ).In the meantime, if we want to verify that a user does <b>NOT</b> have a certain role, how could we do this since we won't be able (until it's fixed I imagine), use :
@Security("not is_granted('MY_SUPER_ROLE')")And the
@IsGranted()annotation does not allow to check a negation (unless it does, but in any case I do not see it in the documentation)Hey Kiuega!
> but if I understand your answer correctly, you would prefer that the expressions be finally moved to custom voters
Yep! That's my general opinion... but I admit that using some basic expressions is probably ok :).
> In the meantime, if we want to verify that a user does NOT have a certain role, how could we do this since we won't be able (until it's fixed I imagine), use :
> @Security("not is_granted('MY_SUPER_ROLE')")
Hmm, I have never done this before! You want to allow access only if they do NOT have a role? Can you explain this a bit further? This sounds a bit odd... bit you might have a legitimate use-case. Anyways, for now, I would just do that logic in my controller in PHP. Or, I might create a voter - especially if it helps my code to read my clearly e..g. @IsGranted('NOT_AN_ADMIN')
Cheers!
Hello !
<blockquote>Can you explain this a bit further?</blockquote>
Yes, quite simply, I have a user who has the <b>ROLE_SUPER_ADMIN</b> role (he's the supreme boss actually), and I only want him to be able to access his sublime admin dashboard, and if he tries to surrender on a page reserved for simple users (those who have the <b>ROLE_USER</b>), then I berate him. This is why I was using the
not is_granted ('ROLE_SUPER_ADMIN')on some routesAnd last question in passing (I hope you don't get bored). Since this new authentication system, the way to treat anonymous users has therefore changed. I saw that there was thus the '<b>PUBLIC_ACCESS</b>', but that seems a little vague to me.
Does making
$this->isGranted ("PUBLIC_ACCESS")make sense?What about the "role" '<b>IS_ANONYMOUS</b>'? Could we still use it?
I find it difficult to know what we can do now to limit access to certain categories of people. For example if I have a custom voter in which I have code that allows me to know if I allow access to someone, according to several conditions:
In this case, how to manage this second part? '<b>PUBLIC_ACCESS</b>', '<b>IS_ANONYMOUS</b>', <u>or pass the token as a parameter and check that it is null</u>, which would mean that it is a guest?
All this gives me lots of doubts about everything I have to change in my code
Hey Kiuega!
> if he tries to surrender on a page reserved for simple users (those who have the ROLE_USER), then I berate him
LOL! Ok, I've never had this use-case, but it makes sense ;)
> Does making $this->isGranted ("PUBLIC_ACCESS") make sense?
> What about the "role" 'IS_ANONYMOUS'? Could we still use it?
Using PUBLIC_ACCESS in that way does not make sense, in the same way that $this->isGranted ("IS_ANONYMOUS") didn't make sense either. Both are only useful (iirc) in access_control, where you add an access_control with PUBLIC_ACCESS to *allow* access to certain URLs (and prevent the rest of the access_control from being parsed, as only the first matched access control wins).
> or pass the token as a parameter and check that it is null, which would mean that it is a guest?
this one :). As I mentioned, those other 2 attributes were only ever useful on access_control. Everywhere else in the system, just check to see if the is a user or not. Oh, and a small detail, iirc, you will be passed a NullToken to your voter (not actually null) if the user is anonymous.
Cheers!
Hello !
Okay great! Thank you very much for your time weaverryan , you have allowed me to learn a lot about this new system! I also hope that this discussion can be of use to others who will drop by in the comments section.
In the end, I created an ExtraVoter in which I will insert reusable general voters. I put the 'is_not_authenticated' there which checks that the token is a NullToken and returns true
Thanks for everything and see you next time! :)
Hey Kiuega!
> I also hope that this discussion can be of use to others who will drop by in the comments section.
Me too! It's one of the reasons I love keeping these conversations in the comments. We index them for the our search engine too... I search for old comments all the time ;).
> Thanks for everything and see you next time! :)
Cheers!
Hey again weaverryan!
After talking with the author, he's aware that configuring a custom rate limiter for login throttling is a bit tricky. He's aware of the issue. For now, he's documenting it - his documentation is similar to what I was telling you, but a bit more complete. Here's the rough draft of it - https://github.com/symfony/...
Let me know if that helps :).
Cheers!
But how Symfony knows which is Authenticator need to send credentials? We can several Authenticators specify in security.yaml
Becuase on login methods i see only
` $error = $authenticationUtils->getLastAuthenticationError();
How it call the Authenticator service? Thanks.
Hey triemli!
Excellent question! On each request, Symfony loops over all of the authenticators that you have defined and calls the
supports()method on ALL of them. Usually, either zero or one authenticator will return true. For example, if the user is sending a POST request to /login, then our LoginFormAuthenticator will return "true" from supports.Once Symfony finds an authenticator whose supports method returns true, it then calls
getCredentials(),getUser()andcheckCredentialson that authenticator. If any of those fail, then it callsonAuthenticationFailureon that one authenticator.On this situation, our
LoginFormAuthenticatordoes TWO important things during that process:1) In
getCredentials()we call$request->getSession()->set(Security::LAST_USERNAME, $credentials['email']);. In your login method, the$authenticationUtils->getLastUsername();is actually a shortcut to READ this key from the session. That is why that logic works inside your login controller method. There's no magic: it's simply that our login authenticator stores the username that was last used in the session, then we read it in the controller.2) In the parent class' onAuthenticationFailure(), the error is also stored in the session - https://github.com/symfony/symfony/blob/6d521d40721104ac684565fe6d1ca2bb0372127e/src/Symfony/Component/Security/Guard/Authenticator/AbstractFormLoginAuthenticator.php#L42. When you call
$authenticationUtils->getLastAuthenticationError();, that is just a shortcut to read that from the session.I hope that clears things up - but please let me know if some parts are still confusing :).
Cheers!
Thanks for the answer! I didn't know that it every authenticator call support() method.
Hi, I have some issue here.
I made /api-login annotation in the SecurityController but I see "too many redirects" an error.
I fixed with
in security yaml, but actually doesn't have an idea what'w wrong was here ;[
Hi triemli!
I think I can explain that :). See the text/video right around this code block: https://symfonycasts.com/screencast/symfony-security/firewalls-authenticator#codeblock-e09d08a770
If you don't have anonymous: true, then it means "don't allow anyone - even anonymous users to access this firewall". I normally recommend that you always have this setting (and then you can restrict access in other ways, like access_control.
But... this may only be part of the answer. I noticed that you created an entirely new firewall for this. Typically (but not always) an application only needs one firewall (if you don't count the sort of, fake, firewall that just prevents you from making the web debug toolbar and profiler not accessible - https://github.com/symfony/recipes/blob/62271a461b640d2d91fd8aaa779f6d65d7a66ec0/symfony/security-bundle/5.1/config/packages/security.yaml#L6-L8 ). On each request, only a single firewall matches - and it checks from top to bottom. So, I DO think that your problem is that you were missing the
anonymous: trueon your firewall. However, I would normally expect you to have only one real firewall and for that one real firewall to have this key - similar to what the default firewall configuration looks like - https://github.com/symfony/recipes/blob/62271a461b640d2d91fd8aaa779f6d65d7a66ec0/symfony/security-bundle/5.1/config/packages/security.yaml#L9-L12Let me know if that helps... or makes any sense. If you have more questions, let me know ;).
Cheers!
Thanks a lot!
That was a problem. I didn't notice that another firewall was created. Before I couldn't open the login page without those lines.
I also tried to say in access_control
{ path: ^/api-login, roles: IS_AUTHENTICATED_ANONYMOUSLY }On example anonymous is <b>true</b> but it works even when i set
anonymous: false(idk why xD):https://pastebin.com/7krLmZ4Z
Now I just added
`access_control:
But I still can open it and method support is works.
Basically, I just wanted to change /login on /api-login. Because it will 2 different auth services. So I couldn't open /api-login
I even didn't reach the <b>support</b> method
Hey triemli
So, is it working now with the code you linked? It looks OK to me, it's basically the default config, you just need to start restricting some paths in the
access_controlsectionThanks! Yes, everything is work good.
But 1 thing:
- { path: ^/api-login, roles: ROLE_ADMIN }access_control rules works for everything except of /api-login page. Even I specifyanonymous: falseit still works and open but only /api-login page.You may have another rule that's overruling it. The access control list reads from top to bottom and the first one always wins.
{ path: ^/api-login, roles: ROLE_ADMIN }means that any URL that starts with "/api-login" it will enforce the user to have the ROLE_ADMIN but it may be happening what I just said. Double-check your other rulesYes, but I have only 1 rule. Also access_control can't be rewritten. Actually I was expected to see access denied but I perfectly open /api-login with no problems as anonym.
Hmm, this is strange! So, if I understand things correctly, you have just ONE access_control, which is:
And you ARE able to access
/api-loginbut you are NOT able to access/api-login/anything-else(this requires ROLE_ADMIN). Is that correct?If so, that's very odd behavior indeed! That access control should match EVERY url starting with
/api-loginincluding the exact URL/api-login. Could you post your fullsecurity.yamlby chance?Cheers!
Before probably was aggressive cache. I manually removed var folder
This is full security.yaml
https://pastebin.com/CRQK7u6e
closed: /api-login
closed: /api-login?username=value@val.com
open: /api-login?username=value@val.com&password=pa$$w0rd //I see JSON answer "true"
Hey triemli!
Ok, I think I know what's going on - it's a "flow" that I had not thought about before :). Here is what (I think) happens:
1) When you access /api-login (via any of the examples you gave above), then the one access_control IS activated. At that moment, you are not authenticated yet (even if you send valid authentication, Symfony hasn't checked it yet - it's "lazy" about doing that). But, before denying you access, it tries to see if you *should* be authenticated by calling your authenticator.
2) In the 3rd case above, your authentication IS successful and you are now authenticated.
3) Finally, the access_control (now that it has at least attempted the authentication system) enforces the ROLE_ADMIN. In the first 2 cases above, you are still anonymous, so you are kicked out. In the 3rd case (I'm assuming the value@val.com user has ROLE_ADMIN) you are allowed access.
So, I think it makes sense :). The "normal" rule is that your login page MUST be accessible anonymously (and this is what I was thinking too), but that is NOT the case in an API situation where you can directly attach your credentials to that very same request. Your login page only needs to be anonymously accessible in a traditional form login where an anonymous user literally needs to be able to "load" /login anonymously before submitting the form.
tl;dr this makes sense - but I was thinking that it didn't. My mistake! Keep up the good work :).
Cheers!
Thanks for the answer! Yeah actually make sense then, but a small thing here: this is user *value@val.com* has a ROLE_USER role, he's not an admin ;] In this case then I would expect auth process and deny.
Hey triemli!
Hmm. Ok, yea that (I think) still makes sense. The full process looks like this:
A) The access_control matches. In order to see if the user has access, the security system is started
B) The security system successfully authenticates the user
C) The security system determines the "authentication success JSON" to send back
D) That is returned... and we're done! The original access_control stuff never has a chance to be enforced
I'm only 98% sure this is what's happening... but it makes sense. The /api-login page is never *really* accessed - it starts to be accessed, but is intercepted by the security system and never continues. But, I admit, it's not a flow I had thought of before - I would normally not cover the "login" URL with an access_control (but it is interesting!).
Cheers!
Hi,
When I create my CustomAuthenticator following your example, I get this error-message:
<blockquote>The "App\Security\CustomAuthenticator::getUser()" method must return a UserInterface. You returned "App\Entity\IvyUser".</blockquote>
My getUser method looks like this:
<br />public function getUser($credentials, UserProviderInterface $userProvider)<br />{<br />return $this->ivyUserRepository->findOneBy(['id' => $credentials['id']]);<br />}<br />Any idea what I'm doing wrong?
Sorry for asking, found the solution myself.
Hey andremoens
That's great! Would you like to share with us your solution?
Cheers!
And how I can impliment Google Authenticator?
"Houston: no signs of life"
Start the conversation!