You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{"payload":{"allShortcutsEnabled":false,"fileTree":{"":{"items":[{"name":".github","path":".github","contentType":"directory"},{"name":"_build","path":"_build","contentType":"directory"},{"name":"_images","path":"_images","contentType":"directory"},{"name":"_includes","path":"_includes","contentType":"directory"},{"name":"bundles","path":"bundles","contentType":"directory"},{"name":"components","path":"components","contentType":"directory"},{"name":"configuration","path":"configuration","contentType":"directory"},{"name":"console","path":"console","contentType":"directory"},{"name":"contributing","path":"contributing","contentType":"directory"},{"name":"controller","path":"controller","contentType":"directory"},{"name":"create_framework","path":"create_framework","contentType":"directory"},{"name":"deployment","path":"deployment","contentType":"directory"},{"name":"doctrine","path":"doctrine","contentType":"directory"},{"name":"event_dispatcher","path":"event_dispatcher","contentType":"directory"},{"name":"form","path":"form","contentType":"directory"},{"name":"frontend","path":"frontend","contentType":"directory"},{"name":"getting_started","path":"getting_started","contentType":"directory"},{"name":"http_cache","path":"http_cache","contentType":"directory"},{"name":"introduction","path":"introduction","contentType":"directory"},{"name":"logging","path":"logging","contentType":"directory"},{"name":"messenger","path":"messenger","contentType":"directory"},{"name":"notifier","path":"notifier","contentType":"directory"},{"name":"profiler","path":"profiler","contentType":"directory"},{"name":"quick_tour","path":"quick_tour","contentType":"directory"},{"name":"reference","path":"reference","contentType":"directory"},{"name":"routing","path":"routing","contentType":"directory"},{"name":"security","path":"security","contentType":"directory"},{"name":"serializer","path":"serializer","contentType":"directory"},{"name":"service_container","path":"service_container","contentType":"directory"},{"name":"setup","path":"setup","contentType":"directory"},{"name":"templating","path":"templating","contentType":"directory"},{"name":"testing","path":"testing","contentType":"directory"},{"name":"translation","path":"translation","contentType":"directory"},{"name":"validation","path":"validation","contentType":"directory"},{"name":"workflow","path":"workflow","contentType":"directory"},{"name":".alexrc","path":".alexrc","contentType":"file"},{"name":".doctor-rst.yaml","path":".doctor-rst.yaml","contentType":"file"},{"name":".editorconfig","path":".editorconfig","contentType":"file"},{"name":".gitignore","path":".gitignore","contentType":"file"},{"name":"LICENSE.md","path":"LICENSE.md","contentType":"file"},{"name":"README.markdown","path":"README.markdown","contentType":"file"},{"name":"best_practices.rst","path":"best_practices.rst","contentType":"file"},{"name":"bundles.rst","path":"bundles.rst","contentType":"file"},{"name":"cache.rst","path":"cache.rst","contentType":"file"},{"name":"configuration.rst","path":"configuration.rst","contentType":"file"},{"name":"console.rst","path":"console.rst","contentType":"file"},{"name":"controller.rst","path":"controller.rst","contentType":"file"},{"name":"deployment.rst","path":"deployment.rst","contentType":"file"},{"name":"doctrine.rst","path":"doctrine.rst","contentType":"file"},{"name":"email.rst","path":"email.rst","contentType":"file"},{"name":"event_dispatcher.rst","path":"event_dispatcher.rst","contentType":"file"},{"name":"forms.rst","path":"forms.rst","contentType":"file"},{"name":"frontend.rst","path":"frontend.rst","contentType":"file"},{"name":"http_cache.rst","path":"http_cache.rst","contentType":"file"},{"name":"http_client.rst","path":"http_client.rst","contentType":"file"},{"name":"index.rst","path":"index.rst","contentType":"file"},{"name":"lock.rst","path":"lock.rst","contentType":"file"},{"name":"logging.rst","path":"logging.rst","contentType":"file"},{"name":"mailer.rst","path":"mailer.rst","contentType":"file"},{"name":"mercure.rst","path":"mercure.rst","contentType":"file"},{"name":"messenger.rst","path":"messenger.rst","contentType":"file"},{"name":"migration.rst","path":"migration.rst","contentType":"file"},{"name":"notifier.rst","path":"notifier.rst","contentType":"file"},{"name":"page_creation.rst","path":"page_creation.rst","contentType":"file"},{"name":"performance.rst","path":"performance.rst","contentType":"file"},{"name":"profiler.rst","path":"profiler.rst","contentType":"file"},{"name":"rate_limiter.rst","path":"rate_limiter.rst","contentType":"file"},{"name":"routing.rst","path":"routing.rst","contentType":"file"},{"name":"security.rst","path":"security.rst","contentType":"file"},{"name":"serializer.rst","path":"serializer.rst","contentType":"file"},{"name":"service_container.rst","path":"service_container.rst","contentType":"file"},{"name":"session.rst","path":"session.rst","contentType":"file"},{"name":"setup.rst","path":"setup.rst","contentType":"file"},{"name":"templates.rst","path":"templates.rst","contentType":"file"},{"name":"testing.rst","path":"testing.rst","contentType":"file"},{"name":"translation.rst","path":"translation.rst","contentType":"file"},{"name":"validation.rst","path":"validation.rst","contentType":"file"},{"name":"web_link.rst","path":"web_link.rst","contentType":"file"},{"name":"workflow.rst","path":"workflow.rst","contentType":"file"}],"totalCount":79}},"fileTreeProcessingTime":27.095963,"foldersToFetch":[],"incompleteFileTree":false,"repo":{"id":521583,"defaultBranch":"7.3","name":"symfony-docs","ownerLogin":"symfony","currentUserCanPush":false,"isFork":false,"isEmpty":false,"createdAt":"2010-02-17T08:43:51.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/143937?v=4","public":true,"private":false,"isOrgOwned":true},"codeLineWrapEnabled":false,"symbolsExpanded":false,"treeExpanded":true,"refInfo":{"name":"cb6da5426b1e4dbf77ab6dc5978a3e4320c2331e","listCacheKey":"v0:1748857018.0","canEdit":false,"refType":"tree","currentOid":"cb6da5426b1e4dbf77ab6dc5978a3e4320c2331e"},"path":"security.rst","currentUser":null,"blob":{"rawLines":null,"stylingDirectives":null,"colorizedLines":null,"csv":null,"csvError":null,"dependabotInfo":{"showConfigurationBanner":false,"configFilePath":null,"networkDependabotPath":"/symfony/symfony-docs/network/updates","dismissConfigurationNoticePath":"/settings/dismiss-notice/dependabot_configuration_notice","configurationNoticeDismissed":null},"displayName":"security.rst","displayUrl":"https://github.com/symfony/symfony-docs/blob/cb6da5426b1e4dbf77ab6dc5978a3e4320c2331e/security.rst?raw=true","headerInfo":{"blobSize":"92.8 KB","deleteTooltip":"You must be signed in to make or propose changes","editTooltip":"You must be signed in to make or propose changes","ghDesktopPath":null,"isGitLfs":false,"onBranch":false,"shortPath":"924bbec","siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2Fsymfony%2Fsymfony-docs%2Fblob%2Fcb6da5426b1e4dbf77ab6dc5978a3e4320c2331e%2Fsecurity.rst","isCSV":false,"isRichtext":true,"toc":[{"level":2,"text":"Security","anchor":"security","htmlText":"Security"},{"level":3,"text":"The User","anchor":"the-user","htmlText":"The User"},{"level":4,"text":"Loading the User: The User Provider","anchor":"loading-the-user-the-user-provider","htmlText":"Loading the User: The User Provider"},{"level":4,"text":"Registering the User: Hashing Passwords","anchor":"registering-the-user-hashing-passwords","htmlText":"Registering the User: Hashing Passwords"},{"level":3,"text":"The Firewall","anchor":"the-firewall","htmlText":"The Firewall"},{"level":3,"text":"Authenticating Users","anchor":"authenticating-users","htmlText":"Authenticating Users"},{"level":4,"text":"Form Login","anchor":"form-login","htmlText":"Form Login"},{"level":5,"text":"CSRF Protection in Login Forms","anchor":"csrf-protection-in-login-forms","htmlText":"CSRF Protection in Login Forms"},{"level":4,"text":"JSON Login","anchor":"json-login","htmlText":"JSON Login"},{"level":4,"text":"HTTP Basic","anchor":"http-basic","htmlText":"HTTP Basic"},{"level":4,"text":"Login Link","anchor":"login-link","htmlText":"Login Link"},{"level":4,"text":"X.509 Client Certificates","anchor":"x509-client-certificates","htmlText":"X.509 Client Certificates"},{"level":4,"text":"Remote Users","anchor":"remote-users","htmlText":"Remote Users"},{"level":4,"text":"Limiting Login Attempts","anchor":"limiting-login-attempts","htmlText":"Limiting Login Attempts"},{"level":3,"text":"Logging Out","anchor":"logging-out","htmlText":"Logging Out"},{"level":4,"text":"Customizing Logout","anchor":"customizing-logout","htmlText":"Customizing Logout"},{"level":3,"text":"Fetching the User Object","anchor":"fetching-the-user-object","htmlText":"Fetching the User Object"},{"level":4,"text":"Fetching the User from a Service","anchor":"fetching-the-user-from-a-service","htmlText":"Fetching the User from a Service"},{"level":4,"text":"Fetch the User in a Template","anchor":"fetch-the-user-in-a-template","htmlText":"Fetch the User in a Template"},{"level":3,"text":"Access Control (Authorization)","anchor":"access-control-authorization","htmlText":"Access Control (Authorization)"},{"level":4,"text":"Roles","anchor":"roles","htmlText":"Roles"},{"level":5,"text":"Hierarchical Roles","anchor":"hierarchical-roles","htmlText":"Hierarchical Roles"},{"level":4,"text":"Add Code to Deny Access","anchor":"add-code-to-deny-access","htmlText":"Add Code to Deny Access"},{"level":5,"text":"Securing URL patterns (access_control)","anchor":"securing-url-patterns-access_control","htmlText":"Securing URL patterns (access_control)"},{"level":5,"text":"Securing Controllers and other Code","anchor":"securing-controllers-and-other-code","htmlText":"Securing Controllers and other Code"},{"level":5,"text":"Access Control in Templates","anchor":"access-control-in-templates","htmlText":"Access Control in Templates"},{"level":5,"text":"Securing other Services","anchor":"securing-other-services","htmlText":"Securing other Services"},{"level":4,"text":"Allowing Unsecured Access (i.e. Anonymous Users)","anchor":"allowing-unsecured-access-ie-anonymous-users","htmlText":"Allowing Unsecured Access (i.e. Anonymous Users)"},{"level":4,"text":"Granting Anonymous Users Access in a Custom Voter","anchor":"granting-anonymous-users-access-in-a-custom-voter","htmlText":"Granting Anonymous Users Access in a Custom Voter"},{"level":4,"text":"Setting Individual User Permissions","anchor":"setting-individual-user-permissions","htmlText":"Setting Individual User Permissions"},{"level":4,"text":"Checking to see if a User is Logged In (IS_AUTHENTICATED_FULLY)","anchor":"checking-to-see-if-a-user-is-logged-in-is_authenticated_fully","htmlText":"Checking to see if a User is Logged In (IS_AUTHENTICATED_FULLY)"},{"level":3,"text":"Understanding how Users are Refreshed from the Session","anchor":"understanding-how-users-are-refreshed-from-the-session","htmlText":"Understanding how Users are Refreshed from the Session"},{"level":4,"text":"Comparing Users Manually with EquatableInterface","anchor":"comparing-users-manually-with-equatableinterface","htmlText":"Comparing Users Manually with EquatableInterface"},{"level":3,"text":"Security Events","anchor":"security-events","htmlText":"Security Events"},{"level":4,"text":"Authentication Events","anchor":"authentication-events","htmlText":"Authentication Events"},{"level":4,"text":"Other Events","anchor":"other-events","htmlText":"Other Events"},{"level":3,"text":"Frequently Asked Questions","anchor":"frequently-asked-questions","htmlText":"Frequently Asked Questions"},{"level":3,"text":"Learn More","anchor":"learn-more","htmlText":"Learn More"},{"level":4,"text":"Authentication (Identifying/Logging in the User)","anchor":"authentication-identifyinglogging-in-the-user","htmlText":"Authentication (Identifying/Logging in the User)"},{"level":4,"text":"Authorization (Denying Access)","anchor":"authorization-denying-access","htmlText":"Authorization (Denying Access)"}],"lineInfo":{"truncatedLoc":"2736","truncatedSloc":"2065"},"mode":"file"},"image":false,"isCodeownersFile":null,"isPlain":false,"isValidLegacyIssueTemplate":false,"issueTemplate":null,"discussionTemplate":null,"language":"reStructuredText","languageID":419,"large":false,"planSupportInfo":{"repoIsFork":null,"repoOwnedByCurrentUser":null,"requestFullPath":"/symfony/symfony-docs/blob/cb6da5426b1e4dbf77ab6dc5978a3e4320c2331e/security.rst","showFreeOrgGatedFeatureMessage":null,"showPlanSupportBanner":null,"upgradeDataAttributes":null,"upgradePath":null},"publishBannersInfo":{"dismissActionNoticePath":"/settings/dismiss-notice/publish_action_from_dockerfile","releasePath":"/symfony/symfony-docs/releases/new?marketplace=true","showPublishActionBanner":false},"rawBlobUrl":"https://github.com/symfony/symfony-docs/raw/cb6da5426b1e4dbf77ab6dc5978a3e4320c2331e/security.rst","renderImageOrRaw":false,"richText":"\u003carticle class=\"markdown-body entry-content container-lg\" itemprop=\"text\"\u003e\u003cpre\u003e.. index::\n single: Security\n\n\u003c/pre\u003e\n\u003ca name=\"user-content-security\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eSecurity\u003c/h2\u003e\u003ca id=\"user-content-security\" class=\"anchor\" aria-label=\"Permalink: Security\" href=\"#security\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eSymfony provides many tools to secure your application. Some HTTP-related\nsecurity tools, like \u003ca href=\"#id1\"\u003e\u003cspan id=\"user-content-id2\"\u003e:doc:`secure session cookies \u0026lt;/session\u0026gt;`\u003c/span\u003e\u003c/a\u003e and\n\u003ca href=\"#id3\"\u003e\u003cspan id=\"user-content-id4\"\u003e:doc:`CSRF protection \u0026lt;/security/csrf\u0026gt;`\u003c/span\u003e\u003c/a\u003e are provided by default. The\nSecurityBundle, which you will learn about in this guide, provides all\nauthentication and authorization features needed to secure your\napplication.\u003c/p\u003e\n\u003cp id=\"user-content-security-installation\" dir=\"auto\"\u003eTo get started, install the SecurityBundle:\u003c/p\u003e\n\u003cpre lang=\"terminal\"\u003e$ composer require symfony/security-bundle\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eIf you have \u003ca href=\"#id5\"\u003e\u003cspan id=\"user-content-id6\"\u003e:ref:`Symfony Flex \u0026lt;symfony-flex\u0026gt;`\u003c/span\u003e\u003c/a\u003e installed, this also\ncreates a \u003ccode\u003esecurity.yaml\u003c/code\u003e configuration file for you:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-yaml notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"# config/packages/security.yaml\nsecurity:\n enable_authenticator_manager: true\n # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords\n password_hashers:\n Symfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface: 'auto'\n # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers\n providers:\n users_in_memory: { memory: null }\n firewalls:\n dev:\n pattern: ^/(_(profiler|wdt)|css|images|js)/\n security: false\n main:\n lazy: true\n provider: users_in_memory\n\n # activate different ways to authenticate\n # https://symfony.com/doc/current/security.html#firewalls-authentication\n\n # https://symfony.com/doc/current/security/impersonating_user.html\n # switch_user: true\n\n # Easy way to control access for large sections of your site\n # Note: Only the *first* access control that matches will be used\n access_control:\n # - { path: ^/admin, roles: ROLE_ADMIN }\n # - { path: ^/profile, roles: ROLE_USER }\"\u003e\u003cpre\u003e\u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e config/packages/security.yaml\u003c/span\u003e\n\u003cspan class=\"pl-ent\"\u003esecurity\u003c/span\u003e:\n \u003cspan class=\"pl-ent\"\u003eenable_authenticator_manager\u003c/span\u003e: \u003cspan class=\"pl-c1\"\u003etrue\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords\u003c/span\u003e\n \u003cspan class=\"pl-ent\"\u003epassword_hashers\u003c/span\u003e:\n \u003cspan class=\"pl-ent\"\u003eSymfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface\u003c/span\u003e: \u003cspan class=\"pl-s\"\u003e\u003cspan class=\"pl-pds\"\u003e'\u003c/span\u003eauto\u003cspan class=\"pl-pds\"\u003e'\u003c/span\u003e\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers\u003c/span\u003e\n \u003cspan class=\"pl-ent\"\u003eproviders\u003c/span\u003e:\n \u003cspan class=\"pl-ent\"\u003eusers_in_memory\u003c/span\u003e: \u003cspan class=\"pl-s\"\u003e{ memory: null }\u003c/span\u003e\n \u003cspan class=\"pl-ent\"\u003efirewalls\u003c/span\u003e:\n \u003cspan class=\"pl-ent\"\u003edev\u003c/span\u003e:\n \u003cspan class=\"pl-ent\"\u003epattern\u003c/span\u003e: \u003cspan class=\"pl-s\"\u003e^/(_(profiler|wdt)|css|images|js)/\u003c/span\u003e\n \u003cspan class=\"pl-ent\"\u003esecurity\u003c/span\u003e: \u003cspan class=\"pl-c1\"\u003efalse\u003c/span\u003e\n \u003cspan class=\"pl-ent\"\u003emain\u003c/span\u003e:\n \u003cspan class=\"pl-ent\"\u003elazy\u003c/span\u003e: \u003cspan class=\"pl-c1\"\u003etrue\u003c/span\u003e\n \u003cspan class=\"pl-ent\"\u003eprovider\u003c/span\u003e: \u003cspan class=\"pl-s\"\u003eusers_in_memory\u003c/span\u003e\n\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e activate different ways to authenticate\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e https://symfony.com/doc/current/security.html#firewalls-authentication\u003c/span\u003e\n\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e https://symfony.com/doc/current/security/impersonating_user.html\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e switch_user: true\u003c/span\u003e\n\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e Easy way to control access for large sections of your site\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e Note: Only the *first* access control that matches will be used\u003c/span\u003e\n \u003cspan class=\"pl-ent\"\u003eaccess_control\u003c/span\u003e:\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e - { path: ^/admin, roles: ROLE_ADMIN }\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e#\u003c/span\u003e - { path: ^/profile, roles: ROLE_USER }\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThat's a lot of config! In the next sections, the three main elements are\ndiscussed:\u003c/p\u003e\n\u003cdl\u003e\n\u003cdt\u003e\u003ca href=\"#the-user\"\u003eThe User\u003c/a\u003e (\u003ccode\u003eproviders\u003c/code\u003e)\u003c/dt\u003e\n\u003cdd\u003eAny secured section of your application needs some concept of\na user. The user provider loads users from any storage (e.g. the\ndatabase) based on a \"user identifier\" (e.g. the user's email address);\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#the-firewall\"\u003eThe Firewall\u003c/a\u003e \u0026amp; \u003ca href=\"#authenticating-users\"\u003eAuthenticating Users\u003c/a\u003e (\u003ccode\u003efirewalls\u003c/code\u003e)\u003c/dt\u003e\n\u003cdd\u003eThe firewall is the core of securing your application. Every request\nwithin the firewall is checked if it needs an authenticated user. The\nfirewall also takes care of authenticating this user (e.g. using a\nlogin form);\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#access-control-authorization\"\u003eAccess Control (Authorization)\u003c/a\u003e (\u003ccode\u003eaccess_control\u003c/code\u003e)\u003c/dt\u003e\n\u003cdd\u003eUsing access control and the authorization checker, you control the\nrequired permissions to perform a specific action or visit a specific\nURL.\u003c/dd\u003e\n\u003c/dl\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eCaution!\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eSymfony Security has received major changes in 5.3. This article\nexplains the \u003cem\u003enew authenticator-based\u003c/em\u003e system (identified by the\n\u003ccode\u003eenable_authenticator_manager: true\u003c/code\u003e config option).\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eRefer to the \u003ca href=\"https://symfony.com/doc/5.2/security.html\" rel=\"nofollow\"\u003e5.2 version of this documentation\u003c/a\u003e if you're still using\nthe legacy security system.\u003c/p\u003e\n\u003c/div\u003e\n\u003ca name=\"user-content-the-user\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eThe User\u003c/h3\u003e\u003ca id=\"user-content-the-user\" class=\"anchor\" aria-label=\"Permalink: The User\" href=\"#the-user\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003ePermissions in Symfony are always linked to a user object. If you need to\nsecure (parts of) your application, you need to create a user class. This\nis a class that implements \u003ca href=\"#id7\"\u003e\u003cspan id=\"user-content-id8\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface`\u003c/span\u003e\u003c/a\u003e.\nThis is often a Doctrine entity, but you can also use a dedicated\nSecurity user class.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe easiest way to generate a user class is using the \u003ccode\u003emake:user\u003c/code\u003e command\nfrom the \u003ca href=\"https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html\" rel=\"nofollow\"\u003eMakerBundle\u003c/a\u003e:\u003c/p\u003e\n\u003cpre lang=\"terminal\"\u003e$ php bin/console make:user\n The name of the security user class (e.g. User) [User]:\n \u0026gt; User\n\n Do you want to store user data in the database (via Doctrine)? (yes/no) [yes]:\n \u0026gt; yes\n\n Enter a property name that will be the unique \"display\" name for the user (e.g. email, username, uuid) [email]:\n \u0026gt; email\n\n Will this app need to hash/check user passwords? Choose No if passwords are not needed or will be checked/hashed by some other system (e.g. a single sign-on server).\n\n Does this app need to hash/check user passwords? (yes/no) [yes]:\n \u0026gt; yes\n\n created: src/Entity/User.php\n created: src/Repository/UserRepository.php\n updated: src/Entity/User.php\n updated: config/packages/security.yaml\n\u003c/pre\u003e\n\u003cdiv class=\"highlight highlight-text-html-php notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"// src/Entity/User.php\nnamespace App\\Entity;\n\nuse App\\Repository\\UserRepository;\nuse Doctrine\\ORM\\Mapping as ORM;\nuse Symfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface;\nuse Symfony\\Component\\Security\\Core\\User\\UserInterface;\n\n/**\n * @ORM\\Entity(repositoryClass=UserRepository::class)\n */\nclass User implements UserInterface, PasswordAuthenticatedUserInterface\n{\n /**\n * @ORM\\Id\n * @ORM\\GeneratedValue\n * @ORM\\Column(type=\u0026quot;integer\u0026quot;)\n */\n private $id;\n\n /**\n * @ORM\\Column(type=\u0026quot;string\u0026quot;, length=180, unique=true)\n */\n private $email;\n\n /**\n * @ORM\\Column(type=\u0026quot;json\u0026quot;)\n */\n private $roles = [];\n\n /**\n * @var string The hashed password\n * @ORM\\Column(type=\u0026quot;string\u0026quot;)\n */\n private $password;\n\n public function getId(): ?int\n {\n return $this-\u0026gt;id;\n }\n\n public function getEmail(): ?string\n {\n return $this-\u0026gt;email;\n }\n\n public function setEmail(string $email): self\n {\n $this-\u0026gt;email = $email;\n\n return $this;\n }\n\n /**\n * The public representation of the user (e.g. a username, an email address, etc.)\n *\n * @see UserInterface\n */\n public function getUserIdentifier(): string\n {\n return (string) $this-\u0026gt;email;\n }\n\n /**\n * @deprecated since Symfony 5.3\n */\n public function getUsername(): string\n {\n return (string) $this-\u0026gt;email;\n }\n\n /**\n * @see UserInterface\n */\n public function getRoles(): array\n {\n $roles = $this-\u0026gt;roles;\n // guarantee every user at least has ROLE_USER\n $roles[] = 'ROLE_USER';\n\n return array_unique($roles);\n }\n\n public function setRoles(array $roles): self\n {\n $this-\u0026gt;roles = $roles;\n\n return $this;\n }\n\n /**\n * @see PasswordAuthenticatedUserInterface\n */\n public function getPassword(): string\n {\n return $this-\u0026gt;password;\n }\n\n public function setPassword(string $password): self\n {\n $this-\u0026gt;password = $password;\n\n return $this;\n }\n\n /**\n * Returning a salt is only needed if you are not using a modern\n * hashing algorithm (e.g. bcrypt or sodium) in your security.yaml.\n *\n * @see UserInterface\n */\n public function getSalt(): ?string\n {\n return null;\n }\n\n /**\n * @see UserInterface\n */\n public function eraseCredentials()\n {\n // If you store any temporary, sensitive data on the user, clear it here\n // $this-\u0026gt;plainPassword = null;\n }\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-c\"\u003e// src/Entity/User.php\u003c/span\u003e\n\u003cspan class=\"pl-k\"\u003enamespace\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eApp\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eEntity\u003c/span\u003e;\n\n\u003cspan class=\"pl-k\"\u003euse\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eApp\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eRepository\u003c/span\u003e\\\u003cspan class=\"pl-smi\"\u003eUserRepository\u003c/span\u003e;\n\u003cspan class=\"pl-k\"\u003euse\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eDoctrine\u003c/span\u003e\\\u003cspan class=\"pl-c1\"\u003eORM\u003c/span\u003e\\\u003cspan class=\"pl-smi\"\u003eMapping\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eas\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003eORM\u003c/span\u003e;\n\u003cspan class=\"pl-k\"\u003euse\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eSymfony\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eComponent\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eSecurity\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eCore\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eUser\u003c/span\u003e\\\u003cspan class=\"pl-smi\"\u003ePasswordAuthenticatedUserInterface\u003c/span\u003e;\n\u003cspan class=\"pl-k\"\u003euse\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eSymfony\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eComponent\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eSecurity\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eCore\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eUser\u003c/span\u003e\\\u003cspan class=\"pl-smi\"\u003eUserInterface\u003c/span\u003e;\n\n\u003cspan class=\"pl-c\"\u003e/**\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * @ORM\\Entity(repositoryClass=UserRepository::class)\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e */\u003c/span\u003e\n\u003cspan class=\"pl-k\"\u003eclass\u003c/span\u003e User \u003cspan class=\"pl-k\"\u003eimplements\u003c/span\u003e UserInterface, PasswordAuthenticatedUserInterface\n{\n \u003cspan class=\"pl-c\"\u003e/**\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * @ORM\\Id\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * @ORM\\GeneratedValue\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * @ORM\\Column(type=\"integer\")\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e */\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003eid\u003c/span\u003e;\n\n \u003cspan class=\"pl-c\"\u003e/**\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * @ORM\\Column(type=\"string\", length=180, unique=true)\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e */\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003eemail\u003c/span\u003e;\n\n \u003cspan class=\"pl-c\"\u003e/**\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * @ORM\\Column(type=\"json\")\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e */\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003eroles\u003c/span\u003e = [];\n\n \u003cspan class=\"pl-c\"\u003e/**\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * @var string The hashed password\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * @ORM\\Column(type=\"string\")\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e */\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003epassword\u003c/span\u003e;\n\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efunction\u003c/span\u003e \u003cspan class=\"pl-en\"\u003egetId\u003c/span\u003e(): ?\u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e\n {\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003e\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e\u003c/span\u003e-\u0026gt;\u003cspan class=\"pl-c1\"\u003eid\u003c/span\u003e;\n }\n\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efunction\u003c/span\u003e \u003cspan class=\"pl-en\"\u003egetEmail\u003c/span\u003e(): ?\u003cspan class=\"pl-smi\"\u003estring\u003c/span\u003e\n {\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003e\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e\u003c/span\u003e-\u0026gt;\u003cspan class=\"pl-c1\"\u003eemail\u003c/span\u003e;\n }\n\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efunction\u003c/span\u003e \u003cspan class=\"pl-en\"\u003esetEmail\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003estring\u003c/span\u003e
8000
\u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003eemail\u003c/span\u003e): \u003cspan class=\"pl-smi\"\u003e\u003cspan class=\"pl-smi\"\u003eself\u003c/span\u003e\u003c/span\u003e\n {\n \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003e\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e\u003c/span\u003e-\u0026gt;\u003cspan class=\"pl-c1\"\u003eemail\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003eemail\u003c/span\u003e;\n\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003e\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e\u003c/span\u003e;\n }\n\n \u003cspan class=\"pl-c\"\u003e/**\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * The public representation of the user (e.g. a username, an email address, etc.)\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e *\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * @see UserInterface\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e */\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efunction\u003c/span\u003e \u003cspan class=\"pl-en\"\u003egetUserIdentifier\u003c/span\u003e(): \u003cspan class=\"pl-smi\"\u003estring\u003c/span\u003e\n {\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e (\u003cspan class=\"pl-smi\"\u003estring\u003c/span\u003e) \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003e\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e\u003c/span\u003e-\u0026gt;\u003cspan class=\"pl-c1\"\u003eemail\u003c/span\u003e;\n }\n\n \u003cspan class=\"pl-c\"\u003e/**\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * @deprecated since Symfony 5.3\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e */\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efunction\u003c/span\u003e \u003cspan class=\"pl-en\"\u003egetUsername\u003c/span\u003e(): \u003cspan class=\"pl-smi\"\u003estring\u003c/span\u003e\n {\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e (\u003cspan class=\"pl-smi\"\u003estring\u003c/span\u003e) \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003e\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e\u003c/span\u003e-\u0026gt;\u003cspan class=\"pl-c1\"\u003eemail\u003c/span\u003e;\n }\n\n \u003cspan class=\"pl-c\"\u003e/**\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * @see UserInterface\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e */\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efunction\u003c/span\u003e \u003cspan class=\"pl-en\"\u003egetRoles\u003c/span\u003e(): \u003cspan class=\"pl-smi\"\u003earray\u003c/span\u003e\n {\n \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003eroles\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003e\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e\u003c/span\u003e-\u0026gt;\u003cspan class=\"pl-c1\"\u003eroles\u003c/span\u003e;\n \u003cspan class=\"pl-c\"\u003e// guarantee every user at least has ROLE_USER\u003c/span\u003e\n \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003eroles\u003c/span\u003e[] = \u003cspan class=\"pl-s\"\u003e'\u003cspan class=\"pl-s\"\u003eROLE_USER\u003c/span\u003e'\u003c/span\u003e;\n\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-en\"\u003earray_unique\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003eroles\u003c/span\u003e);\n }\n\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efunction\u003c/span\u003e \u003cspan class=\"pl-en\"\u003esetRoles\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003earray\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003eroles\u003c/span\u003e): \u003cspan class=\"pl-smi\"\u003e\u003cspan class=\"pl-smi\"\u003eself\u003c/span\u003e\u003c/span\u003e\n {\n \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003e\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e\u003c/span\u003e-\u0026gt;\u003cspan class=\"pl-c1\"\u003eroles\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003eroles\u003c/span\u003e;\n\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003e\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e\u003c/span\u003e;\n }\n\n \u003cspan class=\"pl-c\"\u003e/**\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * @see PasswordAuthenticatedUserInterface\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e */\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efunction\u003c/span\u003e \u003cspan class=\"pl-en\"\u003egetPassword\u003c/span\u003e(): \u003cspan class=\"pl-smi\"\u003estring\u003c/span\u003e\n {\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003e\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e\u003c/span\u003e-\u0026gt;\u003cspan class=\"pl-c1\"\u003epassword\u003c/span\u003e;\n }\n\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efunction\u003c/span\u003e \u003cspan class=\"pl-en\"\u003esetPassword\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003estring\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003epassword\u003c/span\u003e): \u003cspan class=\"pl-smi\"\u003e\u003cspan class=\"pl-smi\"\u003eself\u003c/span\u003e\u003c/span\u003e\n {\n \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003e\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e\u003c/span\u003e-\u0026gt;\u003cspan class=\"pl-c1\"\u003epassword\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003epassword\u003c/span\u003e;\n\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003e\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e\u003c/span\u003e;\n }\n\n \u003cspan class=\"pl-c\"\u003e/**\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * Returning a salt is only needed if you are not using a modern\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * hashing algorithm (e.g. bcrypt or sodium) in your security.yaml.\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e *\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * @see UserInterface\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e */\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efunction\u003c/span\u003e \u003cspan class=\"pl-en\"\u003egetSalt\u003c/span\u003e(): ?\u003cspan class=\"pl-smi\"\u003estring\u003c/span\u003e\n {\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n }\n\n \u003cspan class=\"pl-c\"\u003e/**\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e * @see UserInterface\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e */\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efunction\u003c/span\u003e \u003cspan class=\"pl-en\"\u003eeraseCredentials\u003c/span\u003e()\n {\n \u003cspan class=\"pl-c\"\u003e// If you store any temporary, sensitive data on the user, clear it here\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e// $this-\u0026gt;plainPassword = null;\u003c/span\u003e\n }\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cpre\u003e.. versionadded:: 5.3\n\n The :class:`Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\PasswordAuthenticatedUserInterface`\n interface and ``getUserIdentifier()`` method were introduced in Symfony 5.3.\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eIf your user is a Doctrine entity, like in the example above, don't forget\nto create the tables by \u003ca href=\"#id9\"\u003e\u003cspan id=\"user-content-id10\"\u003e:ref:`creating and running a migration \u0026lt;doctrine-creating-the-database-tables-schema\u0026gt;`\u003c/span\u003e\u003c/a\u003e:\u003c/p\u003e\n\u003cpre lang=\"terminal\"\u003e$ php bin/console make:migration\n$ php bin/console doctrine:migrations:migrate\n\u003c/pre\u003e\n\u003ca name=\"user-content-loading-the-user-the-user-provider\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eLoading the User: The User Provider\u003c/h4\u003e\u003ca id=\"user-content-loading-the-user-the-user-provider\" class=\"anchor\" aria-label=\"Permalink: Loading the User: The User Provider\" href=\"#loading-the-user-the-user-provider\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eBesides creating the entity, the \u003ccode\u003emake:user\u003c/code\u003e command also adds config\nfor a user provider in your security configuration:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n security:\n # ...\n\n providers:\n app_user_provider:\n entity:\n class: App\\Entity\\User\n property: email\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;config\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n\n \u0026lt;provider name=\"app_user_provider\"\u0026gt;\n \u0026lt;entity class=\"App\\Entity\\User\" property=\"email\"/\u0026gt;\n \u0026lt;/provider\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use App\\Entity\\User;\n use Symfony\\Config\\SecurityConfig;\n\n return static function (SecurityConfig $security) {\n // ...\n\n $security-\u0026gt;provider('app_user_provider')\n -\u0026gt;entity()\n -\u0026gt;class(User::class)\n -\u0026gt;property('email')\n ;\n };\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eThis user provider knows how to (re)load users from a storage (e.g. a database)\nbased on a \"user identifier\" (e.g. the user's email address or username).\nThe configuration above uses Doctrine to load the \u003ccode\u003eUser\u003c/code\u003e entity using the\n\u003ccode\u003eemail\u003c/code\u003e property as \"user identifier\".\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eUser providers are used in a couple places during the security lifecycle:\u003c/p\u003e\n\u003cdl\u003e\n\u003cdt\u003e\u003cstrong\u003eLoad the User based on an identifier\u003c/strong\u003e\u003c/dt\u003e\n\u003cdd\u003eDuring login (or any other authenticator), the provider loads the user\nbased on the user identifier. Some other features, like\n\u003ca href=\"#id11\"\u003e\u003cspan id=\"user-content-id12\"\u003e:doc:`user impersonation \u0026lt;/security/impersonating_user\u0026gt;`\u003c/span\u003e\u003c/a\u003e and\n\u003ca href=\"#id13\"\u003e\u003cspan id=\"user-content-id14\"\u003e:doc:`Remember Me \u0026lt;/security/remember_me\u0026gt;`\u003c/span\u003e\u003c/a\u003e also use this.\u003c/dd\u003e\n\u003cdt\u003e\u003cstrong\u003eReload the User from the session\u003c/strong\u003e\u003c/dt\u003e\n\u003cdd\u003eAt the beginning of each request, the user is loaded from the\nsession (unless your firewall is \u003ccode\u003estateless\u003c/code\u003e). The provider\n\"refreshes\" the user (e.g. the database is queried again for fresh\ndata) to make sure all user information is up to date (and if\nnecessary, the user is de-authenticated/logged out if something\nchanged). See \u003ca href=\"#id15\"\u003e\u003cspan id=\"user-content-id16\"\u003e:ref:`user_session_refresh`\u003c/span\u003e\u003c/a\u003e for more information about\nthis process.\u003c/dd\u003e\n\u003c/dl\u003e\n\u003cp dir=\"auto\"\u003eSymfony comes with several built-in user providers:\u003c/p\u003e\n\u003cdl\u003e\n\u003cdt\u003e\u003ca href=\"#id17\"\u003e\u003cspan id=\"user-content-id18\"\u003e:ref:`Entity User Provider \u0026lt;security-entity-user-provider\u0026gt;`\u003c/span\u003e\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003eLoads users from a database using \u003ca href=\"#id19\"\u003e\u003cspan id=\"user-content-id20\"\u003e:doc:`Doctrine \u0026lt;/doctrine\u0026gt;`\u003c/span\u003e\u003c/a\u003e;\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#id21\"\u003e\u003cspan id=\"user-content-id22\"\u003e:ref:`LDAP User Provider \u0026lt;security-ldap-user-provider\u0026gt;`\u003c/span\u003e\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003eLoads users from a LDAP server;\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#id23\"\u003e\u003cspan id=\"user-content-id24\"\u003e:ref:`Memory User Provider \u0026lt;security-memory-user-provider\u0026gt;`\u003c/span\u003e\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003eLoads users from a configuration file;\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#id25\"\u003e\u003cspan id=\"user-content-id26\"\u003e:ref:`Chain User Provider \u0026lt;security-chain-user-provider\u0026gt;`\u003c/span\u003e\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003eMerges two or more user providers into a new user provider.\nSince each firewall has exactly \u003cem\u003eone\u003c/em\u003e user provider, you can use this\nto chain multiple providers together.\u003c/dd\u003e\n\u003c/dl\u003e\n\u003cp dir=\"auto\"\u003eThe built-in user providers cover the most common needs for applications, but you\ncan also create your own \u003ca href=\"#id27\"\u003e\u003cspan id=\"user-content-id28\"\u003e:ref:`custom user provider \u0026lt;security-custom-user-provider\u0026gt;`\u003c/span\u003e\u003c/a\u003e.\u003c/p\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eNote\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eSometimes, you need to inject the user provider in another class (e.g.\nin your custom authenticator). All user providers follow this pattern\nfor their service ID: \u003ccode\u003esecurity.user.provider.concrete.\u0026lt;your-provider-name\u0026gt;\u003c/code\u003e\n(where \u003ccode\u003e\u0026lt;your-provider-name\u0026gt;\u003c/code\u003e is the configuration key, e.g.\n\u003ccode\u003eapp_user_provider\u003c/code\u003e). If you only have one user provider, you can autowire\nit using the \u003ca href=\"#id29\"\u003e\u003cspan id=\"user-content-id30\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserProviderInterface`\u003c/span\u003e\u003c/a\u003e\ntype-hint.\u003c/p\u003e\n\u003c/div\u003e\n\u003ca name=\"user-content-registering-the-user-hashing-passwords\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eRegistering the User: Hashing Passwords\u003c/h4\u003e\u003ca id=\"user-content-registering-the-user-hashing-passwords\" class=\"anchor\" aria-label=\"Permalink: Registering the User: Hashing Passwords\" href=\"#registering-the-user-hashing-passwords\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eMany applications require a user to log in with a password. For these\napplications, the SecurityBundle provides password hashing and verification\nfunctionality.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eFirst, make sure your User class implements the\n\u003ca href=\"#id31\"\u003e\u003cspan id=\"user-content-id32\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\PasswordAuthenticatedUserInterface`\u003c/span\u003e\u003c/a\u003e:\u003c/p\u003e\n\u003cpre\u003e// src/Entity/User.php\n\n// ...\nuse Symfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface;\n\nclass User implements UserInterface, PasswordAuthenticatedUserInterface\n{\n // ...\n\n /**\n * @return string the hashed password for this user\n */\n public function getPassword(): string\n {\n return $this-\u0026gt;password;\n }\n}\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eThen, configure which password hasher should be used for this class. If your\n\u003ccode\u003esecurity.yaml\u003c/code\u003e file wasn't already pre-configured, then \u003ccode\u003emake:user\u003c/code\u003e should\nhave done this for you:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n security:\n # ...\n password_hashers:\n # Use native password hasher, which auto-selects and migrates the best\n # possible hashing algorithm (starting from Symfony 5.3 this is \"bcrypt\")\n Symfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface: 'auto'\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;config\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n \u0026lt;!-- Use native password hasher, which auto-selects and migrates the best\n possible hashing algorithm (starting from Symfony 5.3 this is \"bcrypt\") --\u0026gt;\n \u0026lt;password-hasher class=\"Symfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface\" algorithm=\"auto\"/\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use App\\Entity\\User;\n use Symfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface;\n\n return static function (SecurityConfig $security) {\n // ...\n\n // Use native password hasher, which auto-selects and migrates the best\n // possible hashing algorithm (starting from Symfony 5.3 this is \"bcrypt\")\n $security-\u0026gt;passwordHasher(PasswordAuthenticatedUserInterface::class)\n -\u0026gt;algorithm('auto')\n ;\n };\n\n\u003c/pre\u003e\n\u003cpre\u003e.. versionadded:: 5.3\n\n The ``password_hashers`` option was introduced in Symfony 5.3. In previous\n versions it was called ``encoders``.\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eNow that Symfony knows \u003cem\u003ehow\u003c/em\u003e you want to hash the passwords, you can use the\n\u003ccode\u003eUserPasswordHasherInterface\u003c/code\u003e service to do this before saving your users to\nthe database:\u003c/p\u003e\n\u003cpre\u003e// src/Controller/RegistrationController.php\nnamespace App\\Controller;\n\n// ...\nuse Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface;\n\nclass RegistrationController extends AbstractController\n{\n public function index(UserPasswordHasherInterface $passwordHasher)\n {\n // ... e.g. get the user data from a registration form\n $user = new User(...);\n $plaintextPassword = ...;\n\n // hash the password (based on the security.yaml config for the $user class)\n $hashedPassword = $passwordHasher-\u0026gt;hashPassword(\n $user,\n $plaintextPassword\n );\n $user-\u0026gt;setPassword($hashedPassword);\n\n // ...\n }\n}\n\u003c/pre\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eTip\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe \u003ccode\u003emake:registration-form\u003c/code\u003e maker command can help you set-up the\nregistration controller and add features like email address\nverification using the \u003ca href=\"https://github.com/symfonycasts/verify-email-bundle\"\u003eSymfonyCastsVerifyEmailBundle\u003c/a\u003e.\u003c/p\u003e\n\u003cpre lang=\"terminal\"\u003e$ composer require symfonycasts/verify-email-bundle\n$ php bin/console make:registration-form\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eYou can also manually hash a password by running:\u003c/p\u003e\n\u003cpre lang=\"terminal\"\u003e$ php bin/console security:hash-password\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eRead more about all available hashers and password migration in\n\u003ca href=\"#id33\"\u003e\u003cspan id=\"user-content-id34\"\u003e:doc:`security/passwords`\u003c/span\u003e\u003c/a\u003e.\u003c/p\u003e\n\u003ca name=\"user-content-the-firewall\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eThe Firewall\u003c/h3\u003e\u003ca id=\"user-content-the-firewall\" class=\"anchor\" aria-label=\"Permalink: The Firewall\" href=\"#the-firewall\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe \u003ccode\u003efirewalls\u003c/code\u003e section of \u003ccode\u003econfig/packages/security.yaml\u003c/code\u003e is the \u003cem\u003emost\u003c/em\u003e\nimportant section. A \"firewall\" is your authentication system: the firewall\ndefines which parts of your application are secured and \u003cem\u003ehow\u003c/em\u003e your users\nwill be able to authenticate (e.g. login form, API token, etc).\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n security:\n # ...\n firewalls:\n dev:\n pattern: ^/(_(profiler|wdt)|css|images|js)/\n security: false\n main:\n lazy: true\n provider: users_in_memory\n\n # activate different ways to authenticate\n # https://symfony.com/doc/current/security.html#firewalls-authentication\n\n # https://symfony.com/doc/current/security/impersonating_user.html\n # switch_user: true\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;config\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n \u0026lt;firewall name=\"dev\"\n pattern=\"^/(_(profiler|wdt)|css|images|js)/\"\n security=\"false\"/\u0026gt;\n\n \u0026lt;firewall name=\"main\"\n lazy=\"true\"/\u0026gt;\n\n \u0026lt;!-- activate different ways to authenticate\n https://symfony.com/doc/current/security.html#firewalls-authentication --\u0026gt;\n\n \u0026lt;!-- https://symfony.com/doc/current/security/impersonating_user.html --\u0026gt;\n \u0026lt;!-- \u0026lt;switch-user/\u0026gt; --\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use Symfony\\Config\\SecurityConfig;\n\n return static function (SecurityConfig $security) {\n // ...\n $security-\u0026gt;firewall('dev')\n -\u0026gt;pattern('^/(_(profiler|wdt)|css|images|js)/')\n -\u0026gt;security(false)\n ;\n\n $security-\u0026gt;firewall('main')\n -\u0026gt;lazy(true)\n\n // activate different ways to authenticate\n // https://symfony.com/doc/current/security.html#firewalls-authentication\n\n // https://symfony.com/doc/current/security/impersonating_user.html\n // -\u0026gt;switchUser(true)\n ;\n };\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eOnly one firewall is active on each request: Symfony uses the \u003ccode\u003epattern\u003c/code\u003e key\nto find the first match (you can also\n\u003ca href=\"#id35\"\u003e\u003cspan id=\"user-content-id36\"\u003e:doc:`match by host or other things \u0026lt;/security/firewall_restriction\u0026gt;`\u003c/span\u003e\u003c/a\u003e).\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe \u003ccode\u003edev\u003c/code\u003e firewall is really a fake firewall: it makes sure that you\ndon't accidentally block Symfony's dev tools - which live under URLs like\n\u003ccode\u003e/_profiler\u003c/code\u003e and \u003ccode\u003e/_wdt\u003c/code\u003e.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eAll \u003cem\u003ereal\u003c/em\u003e URLs are handled by the \u003ccode\u003emain\u003c/code\u003e firewall (no \u003ccode\u003epattern\u003c/code\u003e key means\nit matches \u003cem\u003eall\u003c/em\u003e URLs). A firewall can have many modes of authentication,\nin other words, it enables many ways to ask the question \"Who are you?\".\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eOften, the user is unknown (i.e. not logged in) when they first visit your\nwebsite. If you visit your homepage right now, you \u003cem\u003ewill\u003c/em\u003e have access and\nyou'll see that you're visiting a page behind the firewall in the toolbar:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/symfony/symfony-docs/blob/cb6da5426b1e4dbf77ab6dc5978a3e4320c2331e/_images/security/anonymous_wdt.png\"\u003e\u003cimg alt=\"/_images/security/anonymous_wdt.png\" src=\"/symfony/symfony-docs/raw/cb6da5426b1e4dbf77ab6dc5978a3e4320c2331e/_images/security/anonymous_wdt.png\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eVisiting a URL under a firewall doesn't necessarily require you to be authenticated\n(e.g. the login form has to be accessible or some parts of your application\nare public). You'll learn how to restrict access to URLs, controllers or\nanything else within your firewall in the \u003ca href=\"#id37\"\u003e\u003cspan id=\"user-content-id38\"\u003e:ref:`access control\n\u0026lt;security-access-control\u0026gt;`\u003c/span\u003e\u003c/a\u003e section.\u003c/p\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eTip\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe \u003ccode\u003elazy\u003c/code\u003e anonymous mode prevents the session from being started if\nthere is no need for authorization (i.e. explicit check for a user\nprivilege). This is important to keep requests cacheable (see\n\u003ca href=\"#id39\"\u003e\u003cspan id=\"user-content-id40\"\u003e:doc:`/http_cache`\u003c/span\u003e\u003c/a\u003e).\u003c/p\u003e\n\u003c/div\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eNote\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eIf you do not see the toolbar, install the \u003ca href=\"#id41\"\u003e\u003cspan id=\"user-content-id42\"\u003e:doc:`profiler \u0026lt;/profiler\u0026gt;`\u003c/span\u003e\u003c/a\u003e\nwith:\u003c/p\u003e\n\u003cpre lang=\"terminal\"\u003e$ composer require --dev symfony/profiler-pack\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eNow that we understand our firewall, the next step is to create a way for your\nusers to authenticate!\u003c/p\u003e\n\u003ca name=\"user-content-authenticating-users\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAuthenticating Users\u003c/h3\u003e\u003ca id=\"user-content-authenticating-users\" class=\"anchor\" aria-label=\"Permalink: Authenticating Users\" href=\"#authenticating-users\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eDuring authentication, the system tries to find a matching user for the\nvisitor of the webpage. Traditionally, this was done using a login form or\na HTTP basic dialog in the browser. However, the SecurityBundle comes with\nmany other authenticators:\u003c/p\u
8000
003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003ca href=\"#form-login\"\u003eForm Login\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#json-login\"\u003eJSON Login\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#http-basic\"\u003eHTTP Basic\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#login-link\"\u003eLogin Link\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#x-509-client-certificates\"\u003eX.509 Client Certificates\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#remote-users\"\u003eRemote users\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#id43\"\u003e\u003cspan id=\"user-content-id44\"\u003e:doc:`Custom Authenticators \u0026lt;/security/custom_authenticator\u0026gt;`\u003c/span\u003e\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eTip\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eIf your application logs users in via a third-party service such as\nGoogle, Facebook or Twitter (social login), check out the \u003ca href=\"https://github.com/hwi/HWIOAuthBundle\"\u003eHWIOAuthBundle\u003c/a\u003e\ncommunity bundle.\u003c/p\u003e\n\u003c/div\u003e\n\u003ca name=\"user-content-form-login\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eForm Login\u003c/h4\u003e\u003ca id=\"user-content-form-login\" class=\"anchor\" aria-label=\"Permalink: Form Login\" href=\"#form-login\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eMost websites have a login form where users authenticate using an\nidentifier (e.g. email address or username) and a password. This\nfunctionality is provided by the \u003cem\u003eform login authenticator\u003c/em\u003e.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eFirst, create a controller for the login form:\u003c/p\u003e\n\u003cpre lang=\"terminal\"\u003e$ php bin/console make:controller Login\n\n created: src/Controller/LoginController.php\n created: templates/login/index.html.twig\n\u003c/pre\u003e\n\u003cdiv class=\"highlight highlight-text-html-php notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"// src/Controller/LoginController.php\nnamespace App\\Controller;\n\nuse Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Symfony\\Component\\Routing\\Annotation\\Route;\n\nclass LoginController extends AbstractController\n{\n #[Route('/login', name: 'app_login')]\n public function index(): Response\n {\n return $this-\u0026gt;render('login/index.html.twig', [\n 'controller_name' =\u0026gt; 'LoginController',\n ]);\n }\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-c\"\u003e// src/Controller/LoginController.php\u003c/span\u003e\n\u003cspan class=\"pl-k\"\u003enamespace\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eApp\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eController\u003c/span\u003e;\n\n\u003cspan class=\"pl-k\"\u003euse\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eSymfony\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eBundle\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eFrameworkBundle\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eController\u003c/span\u003e\\\u003cspan class=\"pl-smi\"\u003eAbstractController\u003c/span\u003e;\n\u003cspan class=\"pl-k\"\u003euse\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eSymfony\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eComponent\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eHttpFoundation\u003c/span\u003e\\\u003cspan class=\"pl-smi\"\u003eResponse\u003c/span\u003e;\n\u003cspan class=\"pl-k\"\u003euse\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eSymfony\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eComponent\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eRouting\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eAnnotation\u003c/span\u003e\\\u003cspan class=\"pl-smi\"\u003eRoute\u003c/span\u003e;\n\n\u003cspan class=\"pl-k\"\u003eclass\u003c/span\u003e LoginController \u003cspan class=\"pl-k\"\u003eextends\u003c/span\u003e AbstractController\n{\n #[Route(\u003cspan class=\"pl-s\"\u003e'\u003cspan class=\"pl-s\"\u003e/login\u003c/span\u003e'\u003c/span\u003e, name: \u003cspan class=\"pl-s\"\u003e'\u003cspan class=\"pl-s\"\u003eapp_login\u003c/span\u003e'\u003c/span\u003e)]\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efunction\u003c/span\u003e \u003cspan class=\"pl-en\"\u003eindex\u003c/span\u003e(): \u003cspan class=\"pl-smi\"\u003e\u003cspan class=\"pl-smi\"\u003eResponse\u003c/span\u003e\u003c/span\u003e\n {\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003e\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e\u003c/span\u003e-\u0026gt;\u003cspan class=\"pl-en\"\u003erender\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e'\u003cspan class=\"pl-s\"\u003elogin/index.html.twig\u003c/span\u003e'\u003c/span\u003e, [\n \u003cspan class=\"pl-s\"\u003e'\u003cspan class=\"pl-s\"\u003econtroller_name\u003c/span\u003e'\u003c/span\u003e =\u0026gt; \u003cspan class=\"pl-s\"\u003e'\u003cspan class=\"pl-s\"\u003eLoginController\u003c/span\u003e'\u003c/span\u003e,\n ]);\n }\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThen, enable the form login authenticator using the \u003ccode\u003eform_login\u003c/code\u003e setting:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n security:\n # ...\n\n firewalls:\n main:\n # ...\n form_login:\n # \"app_login\" is the name of the route created previously\n login_path: app_login\n check_path: app_login\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;config\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n \u0026lt;firewall name=\"main\"\u0026gt;\n \u0026lt;!-- \"app_login\" is the name of the route created previously --\u0026gt;\n \u0026lt;form-login login-path=\"app_login\" check-path=\"app_login\"/\u0026gt;\n \u0026lt;/firewall\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use Symfony\\Config\\SecurityConfig;\n\n return static function (SecurityConfig $security) {\n // ...\n\n $mainFirewall = $security-\u0026gt;firewall('main');\n\n // \"app_login\" is the name of the route created previously\n $mainFirewall-\u0026gt;formLogin()\n -\u0026gt;loginPath('app_login')\n -\u0026gt;checkPath('app_login')\n ;\n };\n\n\u003c/pre\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eNote\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe \u003ccode\u003elogin_path\u003c/code\u003e and \u003ccode\u003echeck_path\u003c/code\u003e support URLs and route names (but\ncannot have mandatory wildcards - e.g. \u003ccode\u003e/login/{foo}\u003c/code\u003e where \u003ccode\u003efoo\u003c/code\u003e\nhas no default value).\u003c/p\u003e\n\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eOnce enabled, the security system redirects unauthenticated visitors to the\n\u003ccode\u003elogin_path\u003c/code\u003e when they try to access a secured place (this behavior can\nbe customized using \u003ca href=\"#id45\"\u003e\u003cspan id=\"user-content-id46\"\u003e:ref:`authentication entry points \u0026lt;security-entry-point\u0026gt;`\u003c/span\u003e\u003c/a\u003e).\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eEdit the login controller to render the login form:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-diff notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\" // ...\n+ use Symfony\\Component\\Security\\Http\\Authentication\\AuthenticationUtils;\n\n class LoginController extends AbstractController\n {\n #[Route('/login', name: 'app_login')]\n- public function index(): Response\n+ public function index(AuthenticationUtils $authenticationUtils): Response\n {\n+ // get the login error if there is one\n+ $error = $authenticationUtils-\u0026gt;getLastAuthenticationError();\n+\n+ // last username entered by the user\n+ $lastUsername = $authenticationUtils-\u0026gt;getLastUsername();\n+\n return $this-\u0026gt;render('login/index.html.twig', [\n- 'controller_name' =\u0026gt; 'LoginController',\n+ 'last_username' =\u0026gt; $lastUsername,\n+ 'error' =\u0026gt; $error,\n ]);\n }\n }\"\u003e\u003cpre\u003e // ...\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e use Symfony\\Component\\Security\\Http\\Authentication\\AuthenticationUtils;\u003c/span\u003e\n\n class LoginController extends AbstractController\n {\n #[Route('/login', name: 'app_login')]\n\u003cspan class=\"pl-md\"\u003e\u003cspan class=\"pl-md\"\u003e-\u003c/span\u003e public function index(): Response\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e public function index(AuthenticationUtils $authenticationUtils): Response\u003c/span\u003e\n {\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e // get the login error if there is one\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e $error = $authenticationUtils-\u0026gt;getLastAuthenticationError();\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e // last username entered by the user\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e $lastUsername = $authenticationUtils-\u0026gt;getLastUsername();\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e\u003c/span\u003e\n return $this-\u0026gt;render('login/index.html.twig', [\n\u003cspan class=\"pl-md\"\u003e\u003cspan class=\"pl-md\"\u003e-\u003c/span\u003e 'controller_name' =\u0026gt; 'LoginController',\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e 'last_username' =\u0026gt; $lastUsername,\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e 'error' =\u0026gt; $error,\u003c/span\u003e\n ]);\n }\n }\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eDon't let this controller confuse you. Its job is only to \u003cem\u003erender\u003c/em\u003e the form:\nthe \u003ccode\u003eform_login\u003c/code\u003e authenticator will handle the form \u003cem\u003esubmission\u003c/em\u003e automatically.\nIf the user submits an invalid email or password, that authenticator will store\nthe error and redirect back to this controller, where we read the error (using\n\u003ccode\u003eAuthenticationUtils\u003c/code\u003e) so that it can be displayed back to the user.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eFinally, create or update the template:\u003c/p\u003e\n\u003cpre lang=\"html+twig\"\u003e{# templates/login/index.html.twig #}\n{% extends 'base.html.twig' %}\n\n{# ... #}\n\n{% block body %}\n {% if error %}\n \u0026lt;div\u0026gt;{{ error.messageKey|trans(error.messageData, 'security') }}\u0026lt;/div\u0026gt;\n {% endif %}\n\n \u0026lt;form action=\"{{ path('app_login') }}\" method=\"post\"\u0026gt;\n \u0026lt;label for=\"username\"\u0026gt;Email:\u0026lt;/label\u0026gt;\n \u0026lt;input type=\"text\" id=\"username\" name=\"_username\" value=\"{{ last_username }}\"/\u0026gt;\n\n \u0026lt;label for=\"password\"\u0026gt;Password:\u0026lt;/label\u0026gt;\n \u0026lt;input type=\"password\" id=\"password\" name=\"_password\"/\u0026gt;\n\n {# If you want to control the URL the user is redirected to on success\n \u0026lt;input type=\"hidden\" name=\"_target_path\" value=\"/account\"/\u0026gt; #}\n\n \u0026lt;button type=\"submit\"\u0026gt;login\u0026lt;/button\u0026gt;\n \u0026lt;/form\u0026gt;\n{% endblock %}\n\u003c/pre\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eCaution!\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe \u003ccode\u003eerror\u003c/code\u003e variable passed into the template is an instance of\n\u003ca href=\"#id47\"\u003e\u003cspan id=\"user-content-id48\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Core\\\\Exception\\\\AuthenticationException`\u003c/span\u003e\u003c/a\u003e.\nIt may contain sensitive information about the authentication failure.\n\u003cem\u003eNever\u003c/em\u003e use \u003ccode\u003eerror.message\u003c/code\u003e: use the \u003ccode\u003emessageKey\u003c/code\u003e property instead,\nas shown in the example. This message is always safe to display.\u003c/p\u003e\n\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe form can look like anything, but it usually follows some conventions:\u003c/p\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eThe \u003ccode\u003e\u0026lt;form\u0026gt;\u003c/code\u003e element sends a \u003ccode\u003ePOST\u003c/code\u003e request to the \u003ccode\u003eapp_login\u003c/code\u003e route, since\nthat's what you configured as the \u003ccode\u003echeck_path\u003c/code\u003e under the \u003ccode\u003eform_login\u003c/code\u003e key in\n\u003ccode\u003esecurity.yaml\u003c/code\u003e;\u003c/li\u003e\n\u003cli\u003eThe username (or whatever your user's \"identifier\" is, like an email) field has\nthe name \u003ccode\u003e_username\u003c/code\u003e and the password field has the name \u003ccode\u003e_password\u003c/code\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eTip\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eActually, all of this can be configured under the \u003ccode\u003eform_login\u003c/code\u003e key. See\n\u003ca href=\"#id49\"\u003e\u003cspan id=\"user-content-id50\"\u003e:ref:`reference-security-firewall-form-login`\u003c/span\u003e\u003c/a\u003e for more details.\u003c/p\u003e\n\u003c/div\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eCaution!\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThis login form is currently not protected against CSRF attacks. Read\n\u003ca href=\"#id51\"\u003e\u003cspan id=\"user-content-id52\"\u003e:ref:`form_login-csrf`\u003c/span\u003e\u003c/a\u003e on how to protect your login form.\u003c/p\u003e\n\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eAnd that's it! When you submit the form, the security system automatically\nreads the \u003ccode\u003e_username\u003c/code\u003e and \u003ccode\u003e_password\u003c/code\u003e POST parameter, loads the user via\nthe user provider, checks the user's credentials and either authenticates the\nuser or sends them back to the login form where the error can be displayed.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eTo review the whole process:\u003c/p\u003e\n\u003col dir=\"auto\"\u003e\n\u003cli\u003eThe user tries to access a resource that is protected (e.g. \u003ccode\u003e/admin\u003c/code\u003e);\u003c/li\u003e\n\u003cli\u003eThe firewall initiates the authentication process by redirecting the\nuser to the login form (\u003ccode\u003e/login\u003c/code\u003e);\u003c/li\u003e\n\u003cli\u003eThe \u003ccode\u003e/login\u003c/code\u003e page renders login form via the route and controller created\nin this example;\u003c/li\u003e\n\u003cli\u003eThe user submits the login form to \u003ccode\u003e/login\u003c/code\u003e;\u003c/li\u003e\n\u003cli\u003eThe security system (i.e. the \u003ccode\u003eform_login\u003c/code\u003e authenticator) intercepts the\nrequest, checks the user's submitted credentials, authenticates the user if\nthey are correct, and sends the user back to the login form if they are not.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cpre\u003e.. seealso::\n\n You can customize the responses on a successful or failed login\n attempt. See :doc:`/security/form_login`.\n\n\u003c/pre\u003e\n\u003ca name=\"user-content-csrf-protection-in-login-forms\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch5 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eCSRF Protection in Login Forms\u003c/h5\u003e\u003ca id=\"user-content-csrf-protection-in-login-forms\" class=\"anchor\" aria-label=\"Permalink: CSRF Protection in Login Forms\" href=\"#csrf-protection-in-login-forms\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ca href=\"https://en.wikipedia.org/wiki/Cross-site_request_forgery#Forging_login_requests\" rel=\"nofollow\"\u003eLogin CSRF attacks\u003c/a\u003e can be prevented using the same technique of adding hidden\nCSRF tokens into the login forms. The Security component already provides CSRF\nprotection, but you need to configure some options before using it.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eFirst, you need to enable CSRF on the form login:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n security:\n # ...\n\n firewalls:\n secured_area:\n # ...\n form_login:\n # ...\n enable_csrf: true\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;config\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n\n \u0026lt;firewall name=\"secured_area\"\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n \u0026lt;form-login enable-csrf=\"true\"/\u0026gt;\n \u0026lt;/firewall\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use Symfony\\Config\\SecurityConfig;\n\n return static function (SecurityConfig $security) {\n // ...\n\n $mainFirewall = $security-\u0026gt;firewall('main');\n $mainFirewall-\u0026gt;formLogin()\n // ...\n -\u0026gt;enableCsrf(true)\n ;\n };\n\n\u003c/pre\u003e\n\u003cp id=\"user-content-csrf-login-template\" dir=\"auto\"\u003eThen, use the \u003ccode\u003ecsrf_token()\u003c/code\u003e function in the Twig template to generate a CSRF\ntoken and store it as a hidden field of the form. By default, the HTML field\nmust be called \u003ccode\u003e_csrf_token\u003c/code\u003e and the string used to generate the value must\nbe \u003ccode\u003eauthenticate\u003c/code\u003e:\u003c/p\u003e\n\u003cpre lang=\"html+twig\"\u003e{# templates/login/index.html.twig #}\n\n{# ... #}\n\u0026lt;form action=\"{{ path('app_login') }}\" method=\"post\"\u0026gt;\n {# ... the login fields #}\n\n \u0026lt;input type=\"hidden\" name=\"_csrf_token\" value=\"{{ csrf_token('authenticate') }}\"\u0026gt;\n\n \u0026lt;button type=\"submit\"\u0026gt;login\u0026lt;/button\u0026gt;\n\u0026lt;/form\u0026gt;\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eAfter this, you have protected your login form against CSRF attacks.\u003c/p\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eTip\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eYou can change the name of the field by setting \u003ccode\u003ecsrf_parameter\u003c/code\u003e and change\nthe token ID by setting \u003ccode\u003ecsrf_token_id\u003c/code\u003e in your configuration. See\n\u003ca href=\"#id53\"\u003e\u003cspan id=\"user-content-id54\"\u003e:ref:`reference-security-firewall-form-login`\u003c/span\u003e\u003c/a\u003e for more details.\u003c/p\u003e\n\u003c/div\u003e\n\u003ca name=\"user-content-json-login\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eJSON Login\u003c/h4\u003e\u003ca id=\"user-content-json-login\" class=\"anchor\" aria-label=\"Permalink: JSON Login\" href=\"#json-login\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eSome applications provide an API that is secured using tokens. These\napplications may use an endpoint that provides these tokens based on a\nusername (or email) and password. The JSON login authenticator helps you create\nthis functionality.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eEnable the authenticator using the \u003ccode\u003ejson_login\u003c/code\u003e setting:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n security:\n # ...\n\n firewalls:\n main:\n # ...\n json_login:\n # api_login is a route we will create below\n check_path: api_login\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;config\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n \u0026lt;firewall name=\"main\"\u0026gt;\n \u0026lt;json-login check-path=\"api_login\"/\u0026gt;\n \u0026lt;/firewall\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use Symfony\\Config\\SecurityConfig;\n\n return static function (SecurityConfig $security) {\n // ...\n\n $mainFirewall = $security-\u0026gt;firewall('main');\n $mainFirewall-\u0026gt;jsonLogin()\n -\u0026gt;checkPath('api_login')\n ;\n };\n\n\u003c/pre\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eNote\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe \u003ccode\u003echeck_path\u003c/code\u003e supports URLs and route names (but cannot have\nmandatory wildcards - e.g. \u003ccode\u003e/login/{foo}\u003c/code\u003e where \u003ccode\u003efoo\u003c/code\u003e has no\ndefault value).\u003c/p\u003e\n\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe authenticator runs when a client request the \u003ccode\u003echeck_path\u003c/code\u003e. First,\ncreate a controller for this path:\u003c/p\u003e\n\u003cpre lang=\"terminal\"\u003e$ php bin/console make:controller --no-template ApiLogin\n\n created: src/Controller/ApiLoginController.php\n\u003c/pre\u003e\n\u003cdiv class=\"highlight highlight-text-html-php notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"// src/Controller/ApiLoginController.php\nnamespace App\\Controller;\n\nuse Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Symfony\\Component\\Routing\\Annotation\\Route;\n\nclass ApiLoginController extends AbstractController\n{\n #[Route('/api/login', name: 'api_login')]\n public function index(): Response\n {\n return $this-\u0026gt;json([\n 'message' =\u0026gt; 'Welcome to your new controller!',\n 'path' =\u0026gt; 'src/Controller/ApiLoginController.php',\n ]);\n }\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-c\"\u003e// src/Controller/ApiLoginController.php\u003c/span\u003e\n\u003cspan class=\"pl-k\"\u003enamespace\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eApp\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eController\u003c/span\u003e;\n\n\u003cspan class=\"pl-k\"\u003euse\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eSymfony\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eBundle\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eFrameworkBundle\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eController\u003c/span\u003e\\\u003cspan class=\"pl-smi\"\u003eAbstractController\u003c/span\u003e;\n\u003cspan class=\"pl-k\"\u003euse\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eSymfony\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eComponent\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eHttpFoundation\u003c/span\u003e\\\u003cspan class=\"pl-smi\"\u003eResponse\u003c/span\u003e;\n\u003cspan class=\"pl-k\"\u003euse\u003c/span\u003e \u003cspan class=\"pl-v\"\u003eSymfony\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eComponent\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eRouting\u003c/span\u003e\\\u003cspan class=\"pl-v\"\u003eAnnotation\u003c/span\u003e\\\u003cspan class=\"pl-smi\"\u003eRoute\u003c/span\u003e;\n\n\u003cspan class=\"pl-k\"\u003eclass\u003c/span\u003e ApiLoginController \u003cspan class=\"pl-k\"\u003eextends\u003c/span\u003e AbstractController\n{\n #[Route(\u003cspan class=\"pl-s\"\u003e'\u003cspan class=\"pl-s\"\u003e/api/login\u003c/span\u003e'\u003c/span\u003e, name: \u003cspan class=\"pl-s\"\u003e'\u003cspan class=\"pl-s\"\u003eapi_login\u003c/span\u003e'\u003c/span\u003e)]\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efunction\u003c/span\u003e \u003cspan class=\"pl-en\"\u003eindex\u003c/span\u003e(): \u003cspan class=\"pl-smi\"\u003e\u003cspan class=\"pl-smi\"\u003eResponse\u003c/span\u003e\u003c/span\u003e\n {\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003e\u003cspan class=\"pl-c1\"\u003e$\u003c/span\u003e\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e\u003c/span\u003e-\u0026gt;\u003cspan class=\"pl-en\"\u003ejson\u003c/span\u003e([\n \u003cspan class=\"pl-s\"\u003e'\u003cspan class=\"pl-s\"\u003emessage\u003c/span\u003e'\u003c/span\u003e =\u0026gt; \u003cspan class=\"pl-s\"\u003e'\u003cspan class=\"pl-s\"\u003eWelcome to your new controller!\u003c/span\u003e'\u003c/span\u003e,\n \u003cspan class=\"pl-s\"\u003e'\u003cspan class=\"pl-s\"\u003epath\u003c/span\u003e'\u003c/span\u003e =\u0026gt; \u003cspan class=\"pl-s\"\u003e'\u003cspan class=\"pl-s\"\u003esrc/Controller/ApiLoginController.php\u003c/span\u003e'\u003c/span\u003e,\n ]);\n }\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThis login controller will be called after the authenticator successfully\nauthenticates the user. You can get the authenticated user, generate a\ntoken (or whatever you need to return) and return the JSON response:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-diff notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\" // ...\n+ use App\\Entity\\User;\n+ use Symfony\\Component\\Security\\Http\\Attribute\\CurrentUser;\n\n class ApiLoginController extends AbstractController\n {\n #[Route('/api/login', name: 'api_login')]\n- public function index(): Response\n+ public function index(#[CurrentUser] ?User $user): Response\n {\n+ if (null === $user) {\n+ return $this-\u0026gt;json([\n+ 'message' =\u0026gt; 'missing credentials',\n+ ], Response::HTTP_UNAUTHORIZED);\n+ }\n+\n+ $token = ...; // somehow create an API token for $user\n+\n return $this-\u0026gt;json([\n- 'message' =\u0026gt; 'Welcome to your new controller!',\n- 'path' =\u0026gt; 'src/Controller/ApiLoginController.php',\n+ 'user' =\u0026gt; $user-\u0026gt;getUserIdentifier(),\n+ 'token' =\u0026gt; $token,\n ]);\n }\n }\"\u003e\u003cpre\u003e // ...\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e use App\\Entity\\User;\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e use Symfony\\Component\\Security\\Http\\Attribute\\CurrentUser;\u003c/span\u003e\n\n class ApiLoginController extends AbstractController\n {\n #[Route('/api/login', name: 'api_login')]\n\u003cspan class=\"pl-md\"\u003e\u003cspan class=\"pl-md\"\u003e-\u003c/span\u003e public function index(): Response\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e public function index(#[CurrentUser] ?User $user): Response\u003c/span\u003e\n {\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e if (null === $user) {\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan cla
8000
ss=\"pl-mi1\"\u003e+\u003c/span\u003e return $this-\u0026gt;json([\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e 'message' =\u0026gt; 'missing credentials',\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e ], Response::HTTP_UNAUTHORIZED);\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e }\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e $token = ...; // somehow create an API token for $user\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e\u003c/span\u003e\n return $this-\u0026gt;json([\n\u003cspan class=\"pl-md\"\u003e\u003cspan class=\"pl-md\"\u003e-\u003c/span\u003e 'message' =\u0026gt; 'Welcome to your new controller!',\u003c/span\u003e\n\u003cspan class=\"pl-md\"\u003e\u003cspan class=\"pl-md\"\u003e-\u003c/span\u003e 'path' =\u0026gt; 'src/Controller/ApiLoginController.php',\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e 'user' =\u0026gt; $user-\u0026gt;getUserIdentifier(),\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e 'token' =\u0026gt; $token,\u003c/span\u003e\n ]);\n }\n }\u003c/pre\u003e\u003c/div\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eNote\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe \u003ccode\u003e#[CurrentUser]\u003c/code\u003e can only be used in controller arguments to\nretrieve the authenticated user. In services, you would use\n\u003ca href=\"#id55\"\u003e\u003cspan id=\"user-content-id56\"\u003e:method:`Symfony\\\\Component\\\\Security\\\\Core\\\\Security::getUser`\u003c/span\u003e\u003c/a\u003e.\u003c/p\u003e\n\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThat's it! To summarize the process:\u003c/p\u003e\n\u003col dir=\"auto\"\u003e\n\u003cli\u003e\u003cp dir=\"auto\"\u003eA client (e.g. the front-end) makes a \u003cem\u003ePOST request\u003c/em\u003e with the\n\u003ccode\u003eContent-Type: application/json\u003c/code\u003e header to \u003ccode\u003e/api/login\u003c/code\u003e with\n\u003ccode\u003eusername\u003c/code\u003e (even if your identifier is actually an email) and\n\u003ccode\u003epassword\u003c/code\u003e keys:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-json notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"{\n \u0026quot;username\u0026quot;: \u0026quot;dunglas@example.com\u0026quot;,\n \u0026quot;password\u0026quot;: \u0026quot;MyPassword\u0026quot;\n}\"\u003e\u003cpre\u003e{\n \u003cspan class=\"pl-ent\"\u003e\"username\"\u003c/span\u003e: \u003cspan class=\"pl-s\"\u003e\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003edunglas@example.com\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003e\u003c/span\u003e,\n \u003cspan class=\"pl-ent\"\u003e\"password\"\u003c/span\u003e: \u003cspan class=\"pl-s\"\u003e\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003eMyPassword\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003e\u003c/span\u003e\n}\u003c/pre\u003e\u003c/div\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cp dir=\"auto\"\u003eThe security system intercepts the request, checks the user's submitted\ncredentials and authenticates the user. If the credentials is incorrect,\nan HTTP 401 Unauthorized JSON response is returned, otherwise your\ncontroller is run;\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cp dir=\"auto\"\u003eYour controller creates the correct response:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-json notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"{\n \u0026quot;user\u0026quot;: \u0026quot;dunglas@example.com\u0026quot;,\n \u0026quot;token\u0026quot;: \u0026quot;45be42...\u0026quot;\n}\"\u003e\u003cpre\u003e{\n \u003cspan class=\"pl-ent\"\u003e\"user\"\u003c/span\u003e: \u003cspan class=\"pl-s\"\u003e\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003edunglas@example.com\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003e\u003c/span\u003e,\n \u003cspan class=\"pl-ent\"\u003e\"token\"\u003c/span\u003e: \u003cspan class=\"pl-s\"\u003e\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003e45be42...\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003e\u003c/span\u003e\n}\u003c/pre\u003e\u003c/div\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eTip\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe JSON request format can be configured under the \u003ccode\u003ejson_login\u003c/code\u003e key.\nSee \u003ca href=\"#id57\"\u003e\u003cspan id=\"user-content-id58\"\u003e:ref:`reference-security-firewall-json-login`\u003c/span\u003e\u003c/a\u003e for more details.\u003c/p\u003e\n\u003c/div\u003e\n\u003ca name=\"user-content-http-basic\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eHTTP Basic\u003c/h4\u003e\u003ca id=\"user-content-http-basic\" class=\"anchor\" aria-label=\"Permalink: HTTP Basic\" href=\"#http-basic\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ca href=\"https://en.wikipedia.org/wiki/Basic_access_authentication\" rel=\"nofollow\"\u003eHTTP Basic authentication\u003c/a\u003e is a standardized HTTP authentication\nframework. It asks credentials (username and password) using a dialog in\nthe browser and the HTTP basic authenticator of Symfony will verify these\ncredentials.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eAdd the \u003ccode\u003ehttp_basic\u003c/code\u003e key to your firewall to enable HTTP Basic\nauthentication:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n security:\n # ...\n\n firewalls:\n main:\n # ...\n http_basic:\n realm: Secured Area\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;config\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n \u0026lt;firewall name=\"main\"\u0026gt;\n \u0026lt;http-basic realm=\"Secured Area\"/\u0026gt;\n \u0026lt;/firewall\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use Symfony\\Config\\SecurityConfig;\n\n return static function (SecurityConfig $security) {\n $mainFirewall = $security-\u0026gt;firewall('main');\n $mainFirewall-\u0026gt;httpBasic()\n -\u0026gt;realm('Secured Area')\n ;\n };\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eThat's it! Whenever an unauthenticated user tries to visit a protected\npage, Symfony will inform the browser that it needs to start HTTP basic\nauthentication (using the \u003ccode\u003eWWW-Authenticate\u003c/code\u003e response header). Then, the\nauthenticator verifies the credentials and authenticates the user.\u003c/p\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eNote\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eYou cannot use \u003ca href=\"#id59\"\u003e\u003cspan id=\"user-content-id60\"\u003e:ref:`log out \u0026lt;security-logging-out\u0026gt;`\u003c/span\u003e\u003c/a\u003e with the HTTP\nbasic authenticator. Even if you log out from Symfony, your browser\n\"remembers\" your credentials and will send them on every request.\u003c/p\u003e\n\u003c/div\u003e\n\u003ca name=\"user-content-login-link\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eLogin Link\u003c/h4\u003e\u003ca id=\"user-content-login-link\" class=\"anchor\" aria-label=\"Permalink: Login Link\" href=\"#login-link\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eLogin links are a passwordless authentication mechanism. The user will\nreceive a short-lived link (e.g. via email) which will authenticate them to the\nwebsite.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eYou can learn all about this authenticator in \u003ca href=\"#id61\"\u003e\u003cspan id=\"user-content-id62\"\u003e:doc:`/security/login_link`\u003c/span\u003e\u003c/a\u003e.\u003c/p\u003e\n\u003ca name=\"user-content-x-509-client-certificates\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eX.509 Client Certificates\u003c/h4\u003e\u003ca id=\"user-content-x509-client-certificates\" class=\"anchor\" aria-label=\"Permalink: X.509 Client Certificates\" href=\"#x509-client-certificates\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eWhen using client certificates, your web server does all the authentication\nitself. The X.509 authenticator provided by Symfony extracts the email from\nthe \"distinguished name\" (DN) of the client certificate. Then, it uses this\nemail as user identifier in the user provider.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eFirst, configure your web server to enable client certificate verification\nand to expose the certificate's DN to the Symfony application:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: nginx\n\n server {\n # ...\n\n ssl_client_certificate /path/to/my-custom-CA.pem;\n\n # enable client certificate verification\n ssl_verify_client optional;\n ssl_verify_depth 1;\n\n location / {\n # pass the DN as \"SSL_CLIENT_S_DN\" to the application\n fastcgi_param SSL_CLIENT_S_DN $ssl_client_s_dn;\n\n # ...\n }\n }\n\n .. code-block:: apache\n\n # ...\n SSLCACertificateFile \"/path/to/my-custom-CA.pem\"\n SSLVerifyClient optional\n SSLVerifyDepth 1\n\n # pass the DN to the application\n SSLOptions +StdEnvVars\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eThen, enable the X.509 authenticator using \u003ccode\u003ex509\u003c/code\u003e on your firewall:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n security:\n # ...\n\n firewalls:\n main:\n # ...\n x509:\n provider: your_user_provider\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;config\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n\n \u0026lt;firewall name=\"main\"\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n \u0026lt;x509 provider=\"your_user_provider\"/\u0026gt;\n \u0026lt;/firewall\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use Symfony\\Config\\SecurityConfig;\n\n return static function (SecurityConfig $security) {\n $mainFirewall = $security-\u0026gt;firewall('main');\n $mainFirewall-\u0026gt;x509()\n -\u0026gt;provider('your_user_provider')\n ;\n };\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eBy default, Symfony extracts the email address from the DN in two different\nways:\u003c/p\u003e\n\u003col dir=\"auto\"\u003e\n\u003cli\u003eFirst, it tries the \u003ccode\u003eSSL_CLIENT_S_DN_Email\u003c/code\u003e server parameter, which is\nexposed by Apache;\u003c/li\u003e\n\u003cli\u003eIf it is not set (e.g. when using Nginx), it uses \u003ccode\u003eSSL_CLIENT_S_DN\u003c/code\u003e and\nmatches the value following \u003ccode\u003eemailAddress=\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp dir=\"auto\"\u003eYou can customize the name of both parameters under the \u003ccode\u003ex509\u003c/code\u003e key. See\n\u003ca href=\"#id63\"\u003e\u003cspan id=\"user-content-id64\"\u003e:ref:`the configuration reference \u0026lt;reference-security-firewall-x509\u0026gt;`\u003c/span\u003e\u003c/a\u003e for\nmore details.\u003c/p\u003e\n\u003ca name=\"user-content-remote-users\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eRemote Users\u003c/h4\u003e\u003ca id=\"user-content-remote-users\" class=\"anchor\" aria-label=\"Permalink: Remote Users\" href=\"#remote-users\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eBesides client certificate authentication, there are more web server\nmodules that pre-authenticate a user (e.g. kerberos). The remote user\nauthenticator provides a basic integration for these services.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThese modules often expose the authenticated user in the \u003ccode\u003eREMOTE_USER\u003c/code\u003e\nenvironment variable. The remote user authenticator uses this value as the\nuser identifier to load the corresponding user.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eEnable remote user authentication using the \u003ccode\u003eremote_user\u003c/code\u003e key:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n security:\n firewalls:\n main:\n # ...\n remote_user:\n provider: your_user_provider\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;config\u0026gt;\n \u0026lt;firewall name=\"main\"\u0026gt;\n \u0026lt;remote-user provider=\"your_user_provider\"/\u0026gt;\n \u0026lt;/firewall\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use Symfony\\Config\\SecurityConfig;\n\n return static function (SecurityConfig $security) {\n $mainFirewall = $security-\u0026gt;firewall('main');\n $mainFirewall-\u0026gt;remoteUser()\n -\u0026gt;provider('your_user_provider')\n ;\n };\n\n\u003c/pre\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eTip\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eYou can customize the name of this server variable under the\n\u003ccode\u003eremote_user\u003c/code\u003e key. See\n\u003ca href=\"#id65\"\u003e\u003cspan id=\"user-content-id66\"\u003e:ref:`the configuration reference \u0026lt;reference-security-firewall-remote-user\u0026gt;`\u003c/span\u003e\u003c/a\u003e\nfor more details.\u003c/p\u003e\n\u003c/div\u003e\n\u003ca name=\"user-content-limiting-login-attempts\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eLimiting Login Attempts\u003c/h4\u003e\u003ca id=\"user-content-limiting-login-attempts\" class=\"anchor\" aria-label=\"Permalink: Limiting Login Attempts\" href=\"#limiting-login-attempts\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cpre\u003e.. versionadded:: 5.2\n\n Login throttling was introduced in Symfony 5.2.\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eSymfony provides basic protection against \u003ca href=\"https://owasp.org/www-community/controls/Blocking_Brute_Force_Attacks\" rel=\"nofollow\"\u003ebrute force login attacks\u003c/a\u003e.\nYou must enable this using the \u003ccode\u003elogin_throttling\u003c/code\u003e setting:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n security:\n # you must use the authenticator manager\n enable_authenticator_manager: true\n\n firewalls:\n # ...\n\n main:\n # ...\n\n # by default, the feature allows 5 login attempts per minute\n login_throttling: null\n\n # configure the maximum login attempts\n login_throttling:\n max_attempts: 3 # per minute ...\n # interval: '15 minutes' # ... or in a custom period\n\n # use a custom rate limiter via its service ID\n login_throttling:\n limiter: app.my_login_rate_limiter\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\"?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;!-- you must use the authenticator manager --\u0026gt;\n \u0026lt;config enable-authenticator-manager=\"true\"\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n\n \u0026lt;firewall name=\"main\"\u0026gt;\n \u0026lt;!-- by default, the feature allows 5 login attempts per minute\n max-attempts: (optional) You can configure the maximum attempts ...\n interval: (optional) ... and the period of time. --\u0026gt;\n \u0026lt;login-throttling max-attempts=\"3\" interval=\"15 minutes\"/\u0026gt;\n\n \u0026lt;!-- use a custom rate limiter via its service ID --\u0026gt;\n \u0026lt;login-throttling limiter=\"app.my_login_rate_limiter\"/\u0026gt;\n \u0026lt;/firewall\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use Symfony\\Config\\SecurityConfig;\n\n return static function (SecurityConfig $security) {\n $security-\u0026gt;enableAuthenticatorManager(true);\n\n $mainFirewall = $security-\u0026gt;firewall('main');\n\n // by default, the feature allows 5 login attempts per minute\n $mainFirewall-\u0026gt;loginThrottling()\n // -\u0026gt;maxAttempts(3) // Optional: You can configure the maximum attempts ...\n // -\u0026gt;interval('15 minutes') // ... and the period of time.\n ;\n };\n\n\u003c/pre\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eNote\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe value of the \u003ccode\u003einterval\u003c/code\u003e option must be a number followed by any of the\nunits accepted by the \u003ca href=\"https://www.php.net/datetime.formats.relative\" rel=\"nofollow\"\u003ePHP date relative formats\u003c/a\u003e (e.g. \u003ccode\u003e3 seconds\u003c/code\u003e,\n\u003ccode\u003e10 hours\u003c/code\u003e, \u003ccode\u003e1 day\u003c/code\u003e, etc.)\u003c/p\u003e\n\u003c/div\u003e\n\u003cpre\u003e.. versionadded:: 5.3\n\n The ``login_throttling.interval`` option was introduced in Symfony 5.3.\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eInternally, Symfony uses the \u003ca href=\"#id67\"\u003e\u003cspan id=\"user-content-id68\"\u003e:doc:`Rate Limiter component \u0026lt;/rate_limiter\u0026gt;`\u003c/span\u003e\u003c/a\u003e\nwhich by default uses Symfony's cache to store the previous login attempts.\nHowever, you can implement a \u003ca href=\"#id69\"\u003e\u003cspan id=\"user-content-id70\"\u003e:ref:`custom storage \u0026lt;rate-limiter-storage\u0026gt;`\u003c/span\u003e\u003c/a\u003e.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eLogin attempts are limited on \u003ccode\u003emax_attempts\u003c/code\u003e (default: 5)\nfailed requests for \u003ccode\u003eIP address + username\u003c/code\u003e and \u003ccode\u003e5 * max_attempts\u003c/code\u003e\nfailed requests for \u003ccode\u003eIP address\u003c/code\u003e. The second limit protects against an\nattacker using multiple usernames from bypassing the first limit, without\ndisrupting normal users on big networks (such as offices).\u003c/p\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eTip\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eLimiting the failed login attempts is only one basic protection against\nbrute force attacks. The \u003ca href=\"https://owasp.org/www-community/controls/Blocking_Brute_Force_Attacks\" rel=\"nofollow\"\u003eOWASP Brute Force Attacks\u003c/a\u003e guidelines mention\nseveral other protections that you should consider depending on the\nlevel of protection required.\u003c/p\u003e\n\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eIf you need a more complex limiting algorithm, create a class that implements\n\u003ca href=\"#id71\"\u003e\u003cspan id=\"user-content-id72\"\u003e:class:`Symfony\\\\Component\\\\HttpFoundation\\\\RateLimiter\\\\RequestRateLimiterInterface`\u003c/span\u003e\u003c/a\u003e\n(or use\n\u003ca href=\"#id73\"\u003e\u003cspan id=\"user-content-id74\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Http\\\\RateLimiter\\\\DefaultLoginRateLimiter`\u003c/span\u003e\u003c/a\u003e)\nand set the \u003ccode\u003elimiter\u003c/code\u003e option to its service ID:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n framework:\n rate_limiter:\n # define 2 rate limiters (one for username+IP, the other for IP)\n username_ip_login:\n policy: token_bucket\n limit: 5\n rate: { interval: '5 minutes' }\n\n ip_login:\n policy: sliding_window\n limit: 50\n interval: '15 minutes'\n\n services:\n # our custom login rate limiter\n app.login_rate_limiter:\n class: Symfony\\Component\\Security\\Http\\RateLimiter\\DefaultLoginRateLimiter\n arguments:\n # globalFactory is the limiter for IP\n $globalFactory: '@limiter.ip_login'\n # localFactory is the limiter for username+IP\n $localFactory: '@limiter.username_ip_login'\n\n security:\n firewalls:\n main:\n # use a custom rate limiter via its service ID\n login_throttling:\n limiter: app.login_rate_limiter\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\"?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:framework=\"http://symfony.com/schema/dic/symfony\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/symfony\n https://symfony.com/schema/dic/symfony/symfony-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;framework:config\u0026gt;\n \u0026lt;framework:rate-limiter\u0026gt;\n \u0026lt;!-- define 2 rate limiters (one for username+IP, the other for IP) --\u0026gt;\n \u0026lt;framework:limiter name=\"username_ip_login\"\n policy=\"token_bucket\"\n limit=\"5\"\n \u0026gt;\n \u0026lt;framework:rate interval=\"5 minutes\"/\u0026gt;\n \u0026lt;/framework:limiter\u0026gt;\n\n \u0026lt;framework:limiter name=\"ip_login\"\n policy=\"sliding_window\"\n limit=\"50\"\n interval=\"15 minutes\"\n /\u0026gt;\n \u0026lt;/framework:rate-limiter\u0026gt;\n \u0026lt;/framework:config\u0026gt;\n\n \u0026lt;srv:services\u0026gt;\n \u0026lt;!-- our custom login rate limiter --\u0026gt;\n \u0026lt;srv:service id=\"app.login_rate_limiter\"\n class=\"Symfony\\Component\\Security\\Http\\RateLimiter\\DefaultLoginRateLimiter\"\n \u0026gt;\n \u0026lt;!-- 1st argument is the limiter for IP --\u0026gt;\n \u0026lt;srv:argument type=\"service\" id=\"limiter.ip_login\"/\u0026gt;\n \u0026lt;!-- 2nd argument is the limiter for username+IP --\u0026gt;\n \u0026lt;srv:argument type=\"service\" id=\"limiter.username_ip_login\"/\u0026gt;\n \u0026lt;/srv:service\u0026gt;\n \u0026lt;/srv:services\u0026gt;\n\n \u0026lt;config\u0026gt;\n \u0026lt;firewall name=\"main\"\u0026gt;\n \u0026lt;!-- use a custom rate limiter via its service ID --\u0026gt;\n \u0026lt;login-throttling limiter=\"app.login_rate_limiter\"/\u0026gt;\n \u0026lt;/firewall\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use Symfony\\Component\\DependencyInjection\\ContainerBuilder;\n use Symfony\\Component\\DependencyInjection\\Reference;\n use Symfony\\Component\\Security\\Http\\RateLimiter\\DefaultLoginRateLimiter;\n use Symfony\\Config\\FrameworkConfig;\n use Symfony\\Config\\SecurityConfig;\n\n return static function (ContainerBuilder $containerBuilder, FrameworkConfig $framework, SecurityConfig $security) {\n $framework-\u0026gt;rateLimiter()\n -\u0026gt;limiter('username_ip_login')\n -\u0026gt;policy('token_bucket')\n -\u0026gt;limit(5)\n -\u0026gt;rate()\n -\u0026gt;interval('5 minutes')\n ;\n\n $framework-\u0026gt;rateLimiter()\n -\u0026gt;limiter('ip_login')\n -\u0026gt;policy('sliding_window')\n -\u0026gt;limit(50)\n -\u0026gt;interval('15 minutes')\n ;\n\n $containerBuilder-\u0026gt;register('app.login_rate_limiter', DefaultLoginRateLimiter::class)\n -\u0026gt;setArgum
8000
ents([\n // 1st argument is the limiter for IP\n new Reference('limiter.ip_login'),\n // 2nd argument is the limiter for username+IP\n new Reference('limiter.username_ip_login'),\n ]);\n\n $security-\u0026gt;firewall('main')\n -\u0026gt;loginThrottling()\n -\u0026gt;limiter('app.login_rate_limiter')\n ;\n };\n\n\u003c/pre\u003e\n\u003ca name=\"user-content-logging-out\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eLogging Out\u003c/h3\u003e\u003ca id=\"user-content-logging-out\" class=\"anchor\" aria-label=\"Permalink: Logging Out\" href=\"#logging-out\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eTo enable logging out, activate the \u003ccode\u003elogout\u003c/code\u003e config parameter under your firewall:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n security:\n # ...\n\n firewalls:\n main:\n # ...\n logout:\n path: app_logout\n\n # where to redirect after logout\n # target: app_any_route\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;config\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n\n \u0026lt;firewall name=\"main\"\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n \u0026lt;logout path=\"app_logout\"/\u0026gt;\n\n \u0026lt;!-- use \"target\" to configure where to redirect after logout\n \u0026lt;logout path=\"app_logout\" target=\"app_any_route\"/\u0026gt;\n --\u0026gt;\n \u0026lt;/firewall\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use Symfony\\Config\\SecurityConfig;\n\n return static function (SecurityConfig $security) {\n // ...\n\n $mainFirewall = $security-\u0026gt;firewall('main');\n // ...\n $mainFirewall-\u0026gt;logout()\n // the argument can be either a route name or a path\n -\u0026gt;path('app_logout')\n\n // where to redirect after logout\n // -\u0026gt;target('app_any_route')\n ;\n };\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eNext, you need to create a route for this URL (but not a controller):\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: php-annotations\n\n // src/Controller/SecurityController.php\n namespace App\\Controller;\n\n use Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;\n use Symfony\\Component\\Routing\\Annotation\\Route;\n\n class SecurityController extends AbstractController\n {\n /**\n * @Route(\"/logout\", name=\"app_logout\", methods={\"GET\"})\n */\n public function logout(): void\n {\n // controller can be blank: it will never be called!\n throw new \\Exception('Don\\'t forget to activate logout in security.yaml');\n }\n }\n\n .. code-block:: php-attributes\n\n // src/Controller/SecurityController.php\n namespace App\\Controller;\n\n use Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;\n use Symfony\\Component\\Routing\\Annotation\\Route;\n\n class SecurityController extends AbstractController\n {\n #[Route('/logout', name: 'app_logout', methods: ['GET'])]\n public function logout()\n {\n // controller can be blank: it will never be called!\n throw new \\Exception('Don\\'t forget to activate logout in security.yaml');\n }\n }\n\n .. code-block:: yaml\n\n # config/routes.yaml\n app_logout:\n path: /logout\n methods: GET\n\n .. code-block:: xml\n\n \u0026lt;!-- config/routes.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;routes xmlns=\"http://symfony.com/schema/routing\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://symfony.com/schema/routing\n https://symfony.com/schema/routing/routing-1.0.xsd\"\u0026gt;\n\n \u0026lt;route id=\"app_logout\" path=\"/logout\" methods=\"GET\"/\u0026gt;\n \u0026lt;/routes\u0026gt;\n\n .. code-block:: php\n\n // config/routes.php\n use Symfony\\Component\\Routing\\Loader\\Configurator\\RoutingConfigurator;\n\n return function (RoutingConfigurator $routes) {\n $routes-\u0026gt;add('app_logout', '/logout')\n -\u0026gt;methods(['GET'])\n ;\n };\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eThat's it! By sending a user to the \u003ccode\u003eapp_logout\u003c/code\u003e route (i.e. to \u003ccode\u003e/logout\u003c/code\u003e)\nSymfony will un-authenticate the current user and redirect them.\u003c/p\u003e\n\u003ca name=\"user-content-customizing-logout\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eCustomizing Logout\u003c/h4\u003e\u003ca id=\"user-content-customizing-logout\" class=\"anchor\" aria-label=\"Permalink: Customizing Logout\" href=\"#customizing-logout\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cpre\u003e.. versionadded:: 5.1\n\n The ``LogoutEvent`` was introduced in Symfony 5.1. Prior to this\n version, you had to use a\n :ref:`logout success handler \u0026lt;reference-security-logout-success-handler\u0026gt;`\n to customize the logout.\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eIn some cases you need to run extra logic upon logout (e.g. invalidate\nsome tokens) or want to customize what happens after a logout. During\nlogout, a \u003ca href=\"#id75\"\u003e\u003cspan id=\"user-content-id76\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Http\\\\Event\\\\LogoutEvent`\u003c/span\u003e\u003c/a\u003e\nis dispatched. Register an \u003ca href=\"#id77\"\u003e\u003cspan id=\"user-content-id78\"\u003e:doc:`event listener or subscriber \u0026lt;/event_dispatcher\u0026gt;`\u003c/span\u003e\u003c/a\u003e\nto execute custom logic:\u003c/p\u003e\n\u003cpre\u003e// src/EventListener/LogoutSubscriber.php\nnamespace App\\EventListener;\n\nuse Symfony\\Component\\EventDispatcher\\EventSubscriberInterface;\nuse Symfony\\Component\\HttpFoundation\\RedirectResponse;\nuse Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface;\nuse Symfony\\Component\\Security\\Http\\Event\\LogoutEvent;\n\nclass LogoutSubscriber implements EventSubscriberInterface\n{\n public function __construct(\n private UrlGeneratorInterface $urlGenerator\n ) {\n }\n\n public static function getSubscribedEvents(): array\n {\n return [LogoutEvent::class =\u0026gt; 'onLogout'];\n }\n\n public function onLogout(LogoutEvent $event): void\n {\n // get the security token of the session that is about to be logged out\n $token = $event-\u0026gt;getToken();\n\n // get the current request\n $request = $event-\u0026gt;getRequest();\n\n // get the current response, if it is already set by another listener\n $response = $event-\u0026gt;getResponse();\n\n // configure a custom logout response to the homepage\n $response = new RedirectResponse(\n $this-\u0026gt;urlGenerator-\u0026gt;generate('homepage'),\n RedirectResponse::HTTP_SEE_OTHER\n );\n $event-\u0026gt;setResponse($response);\n }\n}\n\u003c/pre\u003e\n\u003ca name=\"user-content-fetching-the-user-object\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFetching the User Object\u003c/h3\u003e\u003ca id=\"user-content-fetching-the-user-object\" class=\"anchor\" aria-label=\"Permalink: Fetching the User Object\" href=\"#fetching-the-user-object\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eAfter authentication, the \u003ccode\u003eUser\u003c/code\u003e object of the current user can be\naccessed via the \u003ccode\u003egetUser()\u003c/code\u003e shortcut in the\n\u003ca href=\"#id79\"\u003e\u003cspan id=\"user-content-id80\"\u003e:ref:`base controller \u0026lt;the-base-controller-class-services\u0026gt;`\u003c/span\u003e\u003c/a\u003e:\u003c/p\u003e\n\u003cpre\u003euse Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;\n\nclass ProfileController extends AbstractController\n{\n public function index(): Response\n {\n // usually you'll want to make sure the user is authenticated first,\n // see \"Authorization\" below\n $this-\u0026gt;denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');\n\n // returns your User object, or null if the user is not authenticated\n // use inline documentation to tell your editor your exact User class\n /** @var \\App\\Entity\\User $user */\n $user = $this-\u0026gt;getUser();\n\n // Call whatever methods you've added to your User class\n // For example, if you added a getFirstName() method, you can use that.\n return new Response('Well hi there '.$user-\u0026gt;getFirstName());\n }\n}\n\u003c/pre\u003e\n\u003ca name=\"user-content-fetching-the-user-from-a-service\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFetching the User from a Service\u003c/h4\u003e\u003ca id=\"user-content-fetching-the-user-from-a-service\" class=\"anchor\" aria-label=\"Permalink: Fetching the User from a Service\" href=\"#fetching-the-user-from-a-service\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eIf you need to get the logged in user from a service, use the\n\u003ca href=\"#id81\"\u003e\u003cspan id=\"user-content-id82\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Core\\\\Security`\u003c/span\u003e\u003c/a\u003e service:\u003c/p\u003e\n\u003cpre\u003e// src/Service/ExampleService.php\n// ...\n\nuse Symfony\\Component\\Security\\Core\\Security;\n\nclass ExampleService\n{\n private $security;\n\n public function __construct(Security $security)\n {\n // Avoid calling getUser() in the constructor: auth may not\n // be complete yet. Instead, store the entire Security object.\n $this-\u0026gt;security = $security;\n }\n\n public function someMethod()\n {\n // returns User object or null if not authenticated\n $user = $this-\u0026gt;security-\u0026gt;getUser();\n\n // ...\n }\n}\n\u003c/pre\u003e\n\u003ca name=\"user-content-fetch-the-user-in-a-template\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFetch the User in a Template\u003c/h4\u003e\u003ca id=\"user-content-fetch-the-user-in-a-template\" class=\"anchor\" aria-label=\"Permalink: Fetch the User in a Template\" href=\"#fetch-the-user-in-a-template\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eIn a Twig Template the user object is available via the \u003ccode\u003eapp.user\u003c/code\u003e variable\nthanks to the \u003ca href=\"#id83\"\u003e\u003cspan id=\"user-content-id84\"\u003e:ref:`Twig global app variable \u0026lt;twig-app-variable\u0026gt;`\u003c/span\u003e\u003c/a\u003e:\u003c/p\u003e\n\u003cpre lang=\"html+twig\"\u003e{% if is_granted('IS_AUTHENTICATED_FULLY') %}\n \u0026lt;p\u0026gt;Email: {{ app.user.email }}\u0026lt;/p\u0026gt;\n{% endif %}\n\u003c/pre\u003e\n\u003ca name=\"user-content-access-control-authorization\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAccess Control (Authorization)\u003c/h3\u003e\u003ca id=\"user-content-access-control-authorization\" class=\"anchor\" aria-label=\"Permalink: Access Control (Authorization)\" href=\"#access-control-authorization\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eUsers can now log in to your app using your login form. Great! Now, you need to learn\nhow to deny access and work with the User object. This is called \u003cstrong\u003eauthorization\u003c/strong\u003e,\nand its job is to decide if a user can access some resource (a URL, a model object,\na method call, ...).\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe process of authorization has two different sides:\u003c/p\u003e\n\u003col dir=\"auto\"\u003e\n\u003cli\u003eThe user receives a specific role when logging in (e.g. \u003ccode\u003eROLE_ADMIN\u003c/code\u003e).\u003c/li\u003e\n\u003cli\u003eYou add code so that a resource (e.g. URL, controller) requires a specific\n\"attribute\" (e.g. a role like \u003ccode\u003eROLE_ADMIN\u003c/code\u003e) in order to be accessed.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ca name=\"user-content-roles\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eRoles\u003c/h4\u003e\u003ca id=\"user-content-roles\" class=\"anchor\" aria-label=\"Permalink: Roles\" href=\"#roles\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eWhen a user logs in, Symfony calls the \u003ccode\u003egetRoles()\u003c/code\u003e method on your \u003ccode\u003eUser\u003c/code\u003e\nobject to determine which roles this user has. In the \u003ccode\u003eUser\u003c/code\u003e class that\nwas generated earlier, the roles are an array that's stored in the\ndatabase and every user is \u003cem\u003ealways\u003c/em\u003e given at least one role: \u003ccode\u003eROLE_USER\u003c/code\u003e:\u003c/p\u003e\n\u003cpre\u003e// src/Entity/User.php\n\n// ...\nclass User\n{\n /**\n * @ORM\\Column(type=\"json\")\n */\n private $roles = [];\n\n // ...\n public function getRoles(): array\n {\n $roles = $this-\u0026gt;roles;\n // guarantee every user at least has ROLE_USER\n $roles[] = 'ROLE_USER';\n\n return array_unique($roles);\n }\n}\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eThis is a nice default, but you can do \u003cem\u003ewhatever\u003c/em\u003e you want to determine which roles\na user should have. The only rule is that every role \u003cstrong\u003emust start with\u003c/strong\u003e the\n\u003ccode\u003eROLE_\u003c/code\u003e prefix - otherwise, things won't work as expected. Other than that,\na role is just a string and you can invent whatever you need (e.g. \u003ccode\u003eROLE_PRODUCT_ADMIN\u003c/code\u003e).\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eYou'll use these roles next to grant access to specific sections of your site.\u003c/p\u003e\n\u003ca name=\"user-content-hierarchical-roles\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch5 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eHierarchical Roles\u003c/h5\u003e\u003ca id=\"user-content-hierarchical-roles\" class=\"anchor\" aria-label=\"Permalink: Hierarchical Roles\" href=\"#hierarchical-roles\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eInstead of giving many roles to each user, you can define role inheritance\nrules by creating a role hierarchy:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n security:\n # ...\n\n role_hierarchy:\n ROLE_ADMIN: ROLE_USER\n ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;config\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n\n \u0026lt;role id=\"ROLE_ADMIN\"\u0026gt;ROLE_USER\u0026lt;/role\u0026gt;\n \u0026lt;role id=\"ROLE_SUPER_ADMIN\"\u0026gt;ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH\u0026lt;/role\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use Symfony\\Config\\SecurityConfig;\n\n return static function (SecurityConfig $security) {\n // ...\n\n $security-\u0026gt;roleHierarchy('ROLE_ADMIN', ['ROLE_USER']);\n $security-\u0026gt;roleHierarchy('ROLE_SUPER_ADMIN', ['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH']);\n };\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eUsers with the \u003ccode\u003eROLE_ADMIN\u003c/code\u003e role will also have the \u003ccode\u003eROLE_USER\u003c/code\u003e role.\nUsers with \u003ccode\u003eROLE_SUPER_ADMIN\u003c/code\u003e, will automatically have \u003ccode\u003eROLE_ADMIN\u003c/code\u003e,\n\u003ccode\u003eROLE_ALLOWED_TO_SWITCH\u003c/code\u003e and \u003ccode\u003eROLE_USER\u003c/code\u003e (inherited from\n\u003ccode\u003eROLE_ADMIN\u003c/code\u003e).\u003c/p\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eCaution!\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eFor role hierarchy to work, do not use \u003ccode\u003e$user-\u0026gt;getRoles()\u003c/code\u003e manually.\nFor example, in a controller extending from the \u003ca href=\"#id85\"\u003e\u003cspan id=\"user-content-id86\"\u003e:ref:`base controller \u0026lt;the-base-controller-class-services\u0026gt;`\u003c/span\u003e\u003c/a\u003e:\u003c/p\u003e\n\u003cpre\u003e// BAD - $user-\u0026gt;getRoles() will not know about the role hierarchy\n$hasAccess = in_array('ROLE_ADMIN', $user-\u0026gt;getRoles());\n\n// GOOD - use of the normal security methods\n$hasAccess = $this-\u0026gt;isGranted('ROLE_ADMIN');\n$this-\u0026gt;denyAccessUnlessGranted('ROLE_ADMIN');\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eNote\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe \u003ccode\u003erole_hierarchy\u003c/code\u003e values are static - you can't, for example, store the\nrole hierarchy in a database. If you need that, create a custom\n\u003ca href=\"#id87\"\u003e\u003cspan id=\"user-content-id88\"\u003e:doc:`security voter \u0026lt;/security/voters\u0026gt;`\u003c/span\u003e\u003c/a\u003e that looks for the user roles\nin the database.\u003c/p\u003e\n\u003c/div\u003e\n\u003ca name=\"user-content-add-code-to-deny-access\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAdd Code to Deny Access\u003c/h4\u003e\u003ca id=\"user-content-add-code-to-deny-access\" class=\"anchor\" aria-label=\"Permalink: Add Code to Deny Access\" href=\"#add-code-to-deny-access\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThere are \u003cstrong\u003etwo\u003c/strong\u003e ways to deny access to something:\u003c/p\u003e\n\u003col dir=\"auto\"\u003e\n\u003cli\u003e\u003ca href=\"#id89\"\u003e\u003cspan id=\"user-content-id90\"\u003e:ref:`access_control in security.yaml \u0026lt;security-authorization-access-control\u0026gt;`\u003c/span\u003e\u003c/a\u003e\nallows you to protect URL patterns (e.g. \u003ccode\u003e/admin/*\u003c/code\u003e). Simpler, but less flexible;\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#id91\"\u003e\u003cspan id=\"user-content-id92\"\u003e:ref:`in your controller (or other code) \u0026lt;security-securing-controller\u0026gt;`\u003c/span\u003e\u003c/a\u003e.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ca name=\"user-content-securing-url-patterns-access-control\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch5 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eSecuring URL patterns (access_control)\u003c/h5\u003e\u003ca id=\"user-content-securing-url-patterns-access_control\" class=\"anchor\" aria-label=\"Permalink: Securing URL patterns (access_control)\" href=\"#securing-url-patterns-access_control\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe most basic way to secure part of your app is to secure an entire URL pattern\nin \u003ccode\u003esecurity.yaml\u003c/code\u003e. For example, to require \u003ccode\u003eROLE_ADMIN\u003c/code\u003e for all URLs that\nstart with \u003ccode\u003e/admin\u003c/code\u003e, you can:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n security:\n # ...\n\n firewalls:\n # ...\n main:\n # ...\n\n access_control:\n # require ROLE_ADMIN for /admin*\n - { path: '^/admin', roles: ROLE_ADMIN }\n\n # or require ROLE_ADMIN or IS_AUTHENTICATED_FULLY for /admin*\n - { path: '^/admin', roles: [IS_AUTHENTICATED_FULLY, ROLE_ADMIN] }\n\n # the 'path' value can be any valid regular expression\n # (this one will match URLs like /api/post/7298 and /api/comment/528491)\n - { path: ^/api/(post|comment)/\\d+$, roles: ROLE_USER }\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;config\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n\n \u0026lt;firewall name=\"main\"\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n \u0026lt;/firewall\u0026gt;\n\n \u0026lt;!-- require ROLE_ADMIN for /admin* --\u0026gt;\n \u0026lt;rule path=\"^/admin\" role=\"ROLE_ADMIN\"/\u0026gt;\n\n \u0026lt;!-- require ROLE_ADMIN or IS_AUTHENTICATED_FULLY for /admin* --\u0026gt;\n \u0026lt;rule path=\"^/admin\"\u0026gt;\n \u0026lt;role\u0026gt;ROLE_ADMIN\u0026lt;/role\u0026gt;\n \u0026lt;role\u0026gt;IS_AUTHENTICATED_FULLY\u0026lt;/role\u0026gt;\n \u0026lt;/rule\u0026gt;\n\n \u0026lt;!-- the 'path' value can be any valid regular expression\n (this one will match URLs like /api/post/7298 and /api/comment/528491) --\u0026gt;\n \u0026lt;rule path=\"^/api/(post|comment)/\\d+$\" role=\"ROLE_USER\"/\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use Symfony\\Config\\SecurityConfig;\n\n return static function (SecurityConfig $security) {\n $security-\u0026gt;enableAuthenticatorManager(true);\n\n // ...\n $security-\u0026gt;firewall('main')\n // ...\n ;\n\n // require ROLE_ADMIN for /admin*\n $security-\u0026gt;accessControl()\n -\u0026gt;path('^/admin')\n -\u0026gt;roles(['ROLE_ADMIN']);\n\n // require ROLE_ADMIN or IS_AUTHENTICATED_FULLY for /admin*\n $security-\u0026gt;accessControl()\n -\u0026gt;path('^/admin')\n -\u0026gt;roles(['ROLE_ADMIN', 'IS_AUTHENTICATED_FULLY']);\n\n // the 'path' value can be any valid regular expression\n // (this one will match URLs like /api/post/7298 and /api/comment/528491)\n $security-\u0026gt;accessControl()\n -\u0026gt;path('^/api/(post|comment)/\\d+$')\n -\u0026gt;roles(['ROL
8000
E_USER']);\n };\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eYou can define as many URL patterns as you need - each is a regular expression.\n\u003cstrong\u003eBUT\u003c/strong\u003e, only \u003cstrong\u003eone\u003c/strong\u003e will be matched per request: Symfony starts at the top of\nthe list and stops when it finds the first match:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n security:\n # ...\n\n access_control:\n # matches /admin/users/*\n - { path: '^/admin/users', roles: ROLE_SUPER_ADMIN }\n\n # matches /admin/* except for anything matching the above rule\n - { path: '^/admin', roles: ROLE_ADMIN }\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;config\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n\n \u0026lt;rule path=\"^/admin/users\" role=\"ROLE_SUPER_ADMIN\"/\u0026gt;\n \u0026lt;rule path=\"^/admin\" role=\"ROLE_ADMIN\"/\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use Symfony\\Config\\SecurityConfig;\n\n return static function (SecurityConfig $security) {\n // ...\n\n $security-\u0026gt;accessControl()\n -\u0026gt;path('^/admin/users')\n -\u0026gt;roles(['ROLE_SUPER_ADMIN']);\n\n $security-\u0026gt;accessControl()\n -\u0026gt;path('^/admin')\n -\u0026gt;roles(['ROLE_ADMIN']);\n };\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003ePrepending the path with \u003ccode\u003e^\u003c/code\u003e means that only URLs \u003cem\u003ebeginning\u003c/em\u003e with the\npattern are matched. For example, a path of \u003ccode\u003e/admin\u003c/code\u003e (without the \u003ccode\u003e^\u003c/code\u003e)\nwould match \u003ccode\u003e/admin/foo\u003c/code\u003e but would also match URLs like \u003ccode\u003e/foo/admin\u003c/code\u003e.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eEach \u003ccode\u003eaccess_control\u003c/code\u003e can also match on IP address, hostname and HTTP methods.\nIt can also be used to redirect a user to the \u003ccode\u003ehttps\u003c/code\u003e version of a URL pattern.\nSee \u003ca href=\"#id93\"\u003e\u003cspan id=\"user-content-id94\"\u003e:doc:`/security/access_control`\u003c/span\u003e\u003c/a\u003e.\u003c/p\u003e\n\u003ca name=\"user-content-securing-controllers-and-other-code\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch5 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eSecuring Controllers and other Code\u003c/h5\u003e\u003ca id=\"user-content-securing-controllers-and-other-code\" class=\"anchor\" aria-label=\"Permalink: Securing Controllers and other Code\" href=\"#securing-controllers-and-other-code\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eYou can deny access from inside a controller:\u003c/p\u003e\n\u003cpre\u003e// src/Controller/AdminController.php\n// ...\n\npublic function adminDashboard(): Response\n{\n $this-\u0026gt;denyAccessUnlessGranted('ROLE_ADMIN');\n\n // or add an optional message - seen by developers\n $this-\u0026gt;denyAccessUnlessGranted('ROLE_ADMIN', null, 'User tried to access a page without having ROLE_ADMIN');\n}\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eThat's it! If access is not granted, a special\n\u003ca href=\"#id95\"\u003e\u003cspan id=\"user-content-id96\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Core\\\\Exception\\\\AccessDeniedException`\u003c/span\u003e\u003c/a\u003e\nis thrown and no more code in your controller is called. Then, one of two things\nwill happen:\u003c/p\u003e\n\u003col dir=\"auto\"\u003e\n\u003cli\u003eIf the user isn't logged in yet, they will be asked to log in (e.g. redirected\nto the login page).\u003c/li\u003e\n\u003cli\u003eIf the user \u003cem\u003eis\u003c/em\u003e logged in, but does \u003cem\u003enot\u003c/em\u003e have the \u003ccode\u003eROLE_ADMIN\u003c/code\u003e role, they'll\nbe shown the 403 access denied page (which you can\n\u003ca href=\"#id97\"\u003e\u003cspan id=\"user-content-id98\"\u003e:ref:`customize \u0026lt;controller-error-pages-by-status-code\u0026gt;`\u003c/span\u003e\u003c/a\u003e).\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp id=\"user-content-security-securing-controller-annotations\" dir=\"auto\"\u003eThanks to the SensioFrameworkExtraBundle, you can also secure your controller\nusing annotations:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: php-annotations\n\n // src/Controller/AdminController.php\n // ...\n\n use Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\IsGranted;\n\n /**\n * Require ROLE_ADMIN for all the actions of this controller\n *\n * @IsGranted(\"ROLE_ADMIN\")\n */\n class AdminController extends AbstractController\n {\n /**\n * Require ROLE_SUPER_ADMIN only for this action\n *\n * @IsGranted(\"ROLE_SUPER_ADMIN\")\n */\n public function adminDashboard(): Response\n {\n // ...\n }\n }\n\n .. code-block:: php-attributes\n\n // src/Controller/AdminController.php\n // ...\n\n use Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\IsGranted;\n\n /**\n * Require ROLE_ADMIN for all the actions of this controller\n */\n #[IsGranted('ROLE_ADMIN')]\n class AdminController extends AbstractController\n {\n /**\n * Require ROLE_SUPER_ADMIN only for this action\n */\n #[IsGranted('ROLE_SUPER_ADMIN')]\n public function adminDashboard(): Response\n {\n // ...\n }\n }\n\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eFor more information, see the \u003ca href=\"https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html\" rel=\"nofollow\"\u003eFrameworkExtraBundle documentation\u003c/a\u003e.\u003c/p\u003e\n\u003ca name=\"user-content-access-control-in-templates\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch5 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAccess Control in Templates\u003c/h5\u003e\u003ca id=\"user-content-access-control-in-templates\" class=\"anchor\" aria-label=\"Permalink: Access Control in Templates\" href=\"#access-control-in-templates\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eIf you want to check if the current user has a certain role, you can use\nthe built-in \u003ccode\u003eis_granted()\u003c/code\u003e helper function in any Twig template:\u003c/p\u003e\n\u003cpre lang=\"html+twig\"\u003e{% if is_granted('ROLE_ADMIN') %}\n \u0026lt;a href=\"...\"\u0026gt;Delete\u0026lt;/a\u0026gt;\n{% endif %}\n\u003c/pre\u003e\n\u003ca name=\"user-content-securing-other-services\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch5 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eSecuring other Services\u003c/h5\u003e\u003ca id=\"user-content-securing-other-services\" class=\"anchor\" aria-label=\"Permalink: Securing other Services\" href=\"#securing-other-services\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eYou can check access \u003cem\u003eanywhere\u003c/em\u003e in your code by injecting the \u003ccode\u003eSecurity\u003c/code\u003e\nservice. For example, suppose you have a \u003ccode\u003eSalesReportManager\u003c/code\u003e service and you\nwant to include extra details only for users that have a \u003ccode\u003eROLE_SALES_ADMIN\u003c/code\u003e role:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-diff notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\" // src/SalesReport/SalesReportManager.php\n\n // ...\n use Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException;\n+ use Symfony\\Component\\Security\\Core\\Security;\n\n class SalesReportManager\n {\n+ private $security;\n\n+ public function __construct(Security $security)\n+ {\n+ $this-\u0026gt;security = $security;\n+ }\n\n public function generateReport()\n {\n $salesData = [];\n\n+ if ($this-\u0026gt;security-\u0026gt;isGranted('ROLE_SALES_ADMIN')) {\n+ $salesData['top_secret_numbers'] = rand();\n+ }\n\n // ...\n }\n\n // ...\n }\"\u003e\u003cpre\u003e // src/SalesReport/SalesReportManager.php\n\n // ...\n use Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException;\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e use Symfony\\Component\\Security\\Core\\Security;\u003c/span\u003e\n\n class SalesReportManager\n {\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e private $security;\u003c/span\u003e\n\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e public function __construct(Security $security)\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e {\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e $this-\u0026gt;security = $security;\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e }\u003c/span\u003e\n\n public function generateReport()\n {\n $salesData = [];\n\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e if ($this-\u0026gt;security-\u0026gt;isGranted('ROLE_SALES_ADMIN')) {\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e $salesData['top_secret_numbers'] = rand();\u003c/span\u003e\n\u003cspan class=\"pl-mi1\"\u003e\u003cspan class=\"pl-mi1\"\u003e+\u003c/span\u003e }\u003c/span\u003e\n\n // ...\n }\n\n // ...\n }\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eIf you're using the \u003ca href=\"#id99\"\u003e\u003cspan id=\"user-content-id100\"\u003e:ref:`default services.yaml configuration \u0026lt;service-container-services-load-example\u0026gt;`\u003c/span\u003e\u003c/a\u003e,\nSymfony will automatically pass the \u003ccode\u003esecurity.helper\u003c/code\u003e to your service\nthanks to autowiring and the \u003ccode\u003eSecurity\u003c/code\u003e type-hint.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eYou can also use a lower-level\n\u003ca href=\"#id101\"\u003e\u003cspan id=\"user-content-id102\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Core\\\\Authorization\\\\AuthorizationCheckerInterface`\u003c/span\u003e\u003c/a\u003e\nservice. It does the same thing as \u003ccode\u003eSecurity\u003c/code\u003e, but allows you to type-hint a\nmore-specific interface.\u003c/p\u003e\n\u003ca name=\"user-content-allowing-unsecured-access-i-e-anonymous-users\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAllowing Unsecured Access (i.e. Anonymous Users)\u003c/h4\u003e\u003ca id=\"user-content-allowing-unsecured-access-ie-anonymous-users\" class=\"anchor\" aria-label=\"Permalink: Allowing Unsecured Access (i.e. Anonymous Users)\" href=\"#allowing-unsecured-access-ie-anonymous-users\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eWhen a visitor isn't yet logged in to your website, they are treated as\n\"unauthenticated\" and don't have any roles. This will block them from\nvisiting your pages if you defined an \u003ccode\u003eaccess_control\u003c/code\u003e rule.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eIn the \u003ccode\u003eaccess_control\u003c/code\u003e configuration, you can use the \u003ccode\u003ePUBLIC_ACCESS\u003c/code\u003e\nsecurity attribute to exclude some routes for unauthenticated access (e.g.\nthe login page):\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/packages/security.yaml\n security:\n enable_authenticator_manager: true\n\n # ...\n access_control:\n # allow unauthenticated users to access the login form\n - { path: ^/admin/login, roles: PUBLIC_ACCESS }\n\n # but require authentication for all other admin routes\n - { path: ^/admin, roles: ROLE_ADMIN }\n\n .. code-block:: xml\n\n \u0026lt;!-- config/packages/security.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\"?\u0026gt;\n \u0026lt;srv:container xmlns=\"http://symfony.com/schema/dic/security\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xmlns:srv=\"http://symfony.com/schema/dic/services\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\n http://symfony.com/schema/dic/security\n https://symfony.com/schema/dic/security/security-1.0.xsd\"\u0026gt;\n\n \u0026lt;config enable-authenticator-manager=\"true\"\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n\n \u0026lt;access-control\u0026gt;\n \u0026lt;!-- allow unauthenticated users to access the login form --\u0026gt;\n \u0026lt;rule path=\"^/admin/login\" role=\"PUBLIC_ACCESS\"/\u0026gt;\n\n \u0026lt;!-- but require authentication for all other admin routes --\u0026gt;\n \u0026lt;rule path=\"^/admin\" role=\"ROLE_ADMIN\"/\u0026gt;\n \u0026lt;/access-control\u0026gt;\n \u0026lt;/config\u0026gt;\n \u0026lt;/srv:container\u0026gt;\n\n .. code-block:: php\n\n // config/packages/security.php\n use Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AuthenticatedVoter;\n use Symfony\\Config\\SecurityConfig;\n\n return static function (SecurityConfig $security) {\n $security-\u0026gt;enableAuthenticatorManager(true);\n // ....\n\n // allow unauthenticated users to access the login form\n $security-\u0026gt;accessControl()\n -\u0026gt;path('^/admin/login')\n -\u0026gt;roles([AuthenticatedVoter::PUBLIC_ACCESS])\n ;\n\n // but require authentication for all other admin routes\n $security-\u0026gt;accessControl()\n -\u0026gt;path('^/admin')\n -\u0026gt;roles(['ROLE_ADMIN'])\n ;\n };\n\n\u003c/pre\u003e\n\u003ca name=\"user-content-granting-anonymous-users-access-in-a-custom-voter\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eGranting Anonymous Users Access in a Custom Voter\u003c/h4\u003e\u003ca id=\"user-content-granting-anonymous-users-access-in-a-custom-voter\" class=\"anchor\" aria-label=\"Permalink: Granting Anonymous Users Access in a Custom Voter\" href=\"#granting-anonymous-users-access-in-a-custom-voter\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eIf you're using a \u003ca href=\"#id103\"\u003e\u003cspan id=\"user-content-id104\"\u003e:doc:`custom voter \u0026lt;/security/voters\u0026gt;`\u003c/span\u003e\u003c/a\u003e, you can allow\nanonymous users access by checking if there is no user set on the token:\u003c/p\u003e\n\u003cpre\u003e// src/Security/PostVoter.php\nnamespace App\\Security;\n\n// ...\nuse Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface;\nuse Symfony\\Component\\Security\\Core\\Authentication\\User\\UserInterface;\nuse Symfony\\Component\\Security\\Core\\Authorization\\Voter\\Voter;\n\nclass PostVoter extends Voter\n{\n // ...\n\n protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool\n {\n // ...\n\n if (!$token-\u0026gt;getUser() instanceof UserInterface) {\n // the user is not authenticated, e.g. only allow them to\n // see public posts\n return $subject-\u0026gt;isPublic();\n }\n }\n}\n\u003c/pre\u003e\n\u003ca name=\"user-content-setting-individual-user-permissions\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eSetting Individual User Permissions\u003c/h4\u003e\u003ca id=\"user-content-setting-individual-user-permissions\" class=\"anchor\" aria-label=\"Permalink: Setting Individual User Permissions\" href=\"#setting-individual-user-permissions\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eMost applications require more specific access rules. For instance, a user\nshould be able to only edit their \u003cem\u003eown\u003c/em\u003e comments on a blog. Voters allow you\nto write \u003cem\u003ewhatever\u003c/em\u003e business logic you need to determine access. Using\nthese voters is similar to the role-based access checks implemented in the\nprevious chapters. Read \u003ca href=\"#id105\"\u003e\u003cspan id=\"user-content-id106\"\u003e:doc:`/security/voters`\u003c/span\u003e\u003c/a\u003e to learn how to implement\nyour own voter.\u003c/p\u003e\n\u003ca name=\"user-content-checking-to-see-if-a-user-is-logged-in-is-authenticated-fully\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eChecking to see if a User is Logged In (IS_AUTHENTICATED_FULLY)\u003c/h4\u003e\u003ca id=\"user-content-checking-to-see-if-a-user-is-logged-in-is_authenticated_fully\" class=\"anchor\" aria-label=\"Permalink: Checking to see if a User is Logged In (IS_AUTHENTICATED_FULLY)\" href=\"#checking-to-see-if-a-user-is-logged-in-is_authenticated_fully\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eIf you \u003cem\u003eonly\u003c/em\u003e want to check if a user is logged in (you don't care about roles),\nyou have the following two options.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eFirstly, if you've given \u003cem\u003eevery\u003c/em\u003e user \u003ccode\u003eROLE_USER\u003c/code\u003e, you can check for that role.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eSecondly, you can use a special \"attribute\" in place of a role:\u003c/p\u003e\n\u003cpre\u003e// ...\n\npublic function adminDashboard(): Response\n{\n $this-\u0026gt;denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');\n\n // ...\n}\n\u003c/pre\u003e\n\u003cp dir=\"auto\"\u003eYou can use \u003ccode\u003eIS_AUTHENTICATED_FULLY\u003c/code\u003e anywhere roles are used: like\n\u003ccode\u003eaccess_control\u003c/code\u003e or in Twig.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003eIS_AUTHENTICATED_FULLY\u003c/code\u003e isn't a role, but it kind of acts like one, and every\nuser that has logged in will have this. Actually, there are some special attributes\nlike this:\u003c/p\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003ccode\u003eIS_AUTHENTICATED_REMEMBERED\u003c/code\u003e: \u003cem\u003eAll\u003c/em\u003e logged in users have this, even\nif they are logged in because of a \"remember me cookie\". Even if you don't\nuse the \u003ca href=\"#id107\"\u003e\u003cspan id=\"user-content-id108\"\u003e:doc:`remember me functionality \u0026lt;/security/remember_me\u0026gt;`\u003c/span\u003e\u003c/a\u003e,\nyou can use this to check if the user is logged in.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eIS_AUTHENTICATED_FULLY\u003c/code\u003e: This is similar to \u003ccode\u003eIS_AUTHENTICATED_REMEMBERED\u003c/code\u003e,\nbut stronger. Users who are logged in only because of a \"remember me cookie\"\nwill have \u003ccode\u003eIS_AUTHENTICATED_REMEMBERED\u003c/code\u003e but will not have \u003ccode\u003eIS_AUTHENTICATED_FULLY\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eIS_REMEMBERED\u003c/code\u003e: \u003cem\u003eOnly\u003c/em\u003e users authenticated using the\n\u003ca href=\"#id109\"\u003e\u003cspan id=\"user-content-id110\"\u003e:doc:`remember me functionality \u0026lt;/security/remember_me\u0026gt;`\u003c/span\u003e\u003c/a\u003e, (i.e. a\nremember-me cookie).\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eIS_IMPERSONATOR\u003c/code\u003e: When the current user is\n\u003ca href=\"#id111\"\u003e\u003cspan id=\"user-content-id112\"\u003e:doc:`impersonating \u0026lt;/security/impersonating_user\u0026gt;`\u003c/span\u003e\u003c/a\u003e another user in this\nsession, this attribute will match.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cpre\u003e.. versionadded:: 5.1\n\n The ``IS_REMEMBERED`` and ``IS_IMPERSONATOR`` attributes were\n introduced in Symfony 5.1.\n\n\u003c/pre\u003e\n\u003cpre\u003e.. deprecated:: 5.3\n\n The ``IS_ANONYMOUS`` and ``IS_AUTHENTICATED_ANONYMOUSLY`` attributes are\n deprecated since Symfony 5.3.\n\n\u003c/pre\u003e\n\u003ca name=\"user-content-understanding-how-users-are-refreshed-from-the-session\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eUnderstanding how Users are Refreshed from the Session\u003c/h3\u003e\u003ca id=\"user-content-understanding-how-users-are-refreshed-from-the-session\" class=\"anchor\" aria-label=\"Permalink: Understanding how Users are Refreshed from the Session\" href=\"#understanding-how-users-are-refreshed-from-the-session\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eAt the end of every request (unless your firewall is \u003ccode\u003estateless\u003c/code\u003e), your\n\u003ccode\u003eUser\u003c/code\u003e object is serialized to the session. At the beginning of the next\nrequest, it's deserialized and then passed to your user provider to \"refresh\" it\n(e.g. Doctrine queries for a fresh user).\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThen, the two User objects (the original from the session and the refreshed User\nobject) are \"compared\" to see if they are \"equal\". By default, the core\n\u003ccode\u003eAbstractToken\u003c/code\u003e class compares the return values of the \u003ccode\u003egetPassword()\u003c/code\u003e,\n\u003ccode\u003egetSalt()\u003c/code\u003e and \u003ccode\u003egetUserIdentifier()\u003c/code\u003e methods. If any of these are different,\nyour user will be logged out. This is a security measure to make sure that malicious\nusers can be de-authenticated if core user data changes.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eHowever, in some cases, this process can cause unexpected authentication problems.\nIf you're having problems authenticating, it could be that you \u003cem\u003eare\u003c/em\u003e authenticating\nsuccessfully, but you immediately lose authentication after the first redirect.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eIn that case, review the serialization logic (e.g. the \u003ccode\u003e__serialize()\u003c/code\u003e or\n\u003ccode\u003eserialize()\u003c/code\u003e methods) on you user class (if you have any) to make sure\nthat all the fields necessary are serialized and also exclude all the\nfields not necessary to be serialized (e.g. Doctrine relations).\u003c/p\u003e\n\u003ca name=\"user-content-comparing-users-manually-with-equatableinterface\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eComparing Users Manually with EquatableInterface\u003c/h4\u003e\u003ca id=\"user-content-comparing-users-manually-with-equatableinterface\" class=\"anchor\" aria-label=\"Permalink: Comparing Users Manually with EquatableInterface\" href=\"#comparing-users-manually-with-equatableinterface\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eOr, if you need more control over the \"compare users\" process, make your User class\nimplement \u003ca href=\"#id113\"\u003e\u003cspan id=\"user-content-id114\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\EquatableInterface`\u003c/span\u003e\u003c/a\u003e.\nThen, your \u003ccode\u003eisEqualTo()\u003c/code\u003e method will be called when comparing users instead\nof the core logic.\u003c/p\u003e\n\u003ca name=\"user-content-security-events\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eSecurity Events\u003c/h3\u003e\u003ca id=\"user-content-security-events\" class=\"anchor\" aria-label=\"Permalink: Security Events\" href=\"#security-events\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eDuring the authentication proce
7046
ss, multiple events are dispatched that allow you\nto hook into the process or customize the response sent back to the user. You\ncan do this by creating an \u003ca href=\"#id115\"\u003e\u003cspan id=\"user-content-id116\"\u003e:doc:`event listener or subscriber \u0026lt;/event_dispatcher\u0026gt;`\u003c/span\u003e\u003c/a\u003e\nfor these events.\u003c/p\u003e\n\u003cdiv dir=\"auto\"\u003e\n\u003cp dir=\"auto\"\u003eTip\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eEvery Security firewall has its own event dispatcher\n(\u003ccode\u003esecurity.event_dispatcher.FIREWALLNAME\u003c/code\u003e). Events are dispatched on\nboth the global and the firewall-specific dispatcher. You can register\non the firewall dispatcher if you want your listener to only be\ncalled for a specific firewall. For instance, if you have an \u003ccode\u003eapi\u003c/code\u003e\nand \u003ccode\u003emain\u003c/code\u003e firewall, use this configuration to register only on the\nlogout event in the \u003ccode\u003emain\u003c/code\u003e firewall:\u003c/p\u003e\n\u003cpre\u003e.. configuration-block::\n\n .. code-block:: yaml\n\n # config/services.yaml\n services:\n # ...\n\n App\\EventListener\\LogoutSubscriber:\n tags:\n - name: kernel.event_subscriber\n dispatcher: security.event_dispatcher.main\n\n .. code-block:: xml\n\n \u0026lt;!-- config/services.xml --\u0026gt;\n \u0026lt;?xml version=\"1.0\" encoding=\"UTF-8\" ?\u0026gt;\n \u0026lt;container xmlns=\"http://symfony.com/schema/dic/services\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://symfony.com/schema/dic/services\n https://symfony.com/schema/dic/services/services-1.0.xsd\"\u0026gt;\n\n \u0026lt;services\u0026gt;\n \u0026lt;!-- ... --\u0026gt;\n\n \u0026lt;service id=\"App\\EventListener\\LogoutSubscriber\"\u0026gt;\n \u0026lt;tag name=\"kernel.event_subscriber\"\n dispatcher=\"security.event_dispatcher.main\"\n /\u0026gt;\n \u0026lt;/service\u0026gt;\n \u0026lt;/services\u0026gt;\n \u0026lt;/container\u0026gt;\n\n .. code-block:: php\n\n // config/services.php\n namespace Symfony\\Component\\DependencyInjection\\Loader\\Configurator;\n\n use App\\EventListener\\LogoutSubscriber;\n\n return function(ContainerConfigurator $containerConfigurator) {\n $services = $containerConfigurator-\u0026gt;services();\n\n $services-\u0026gt;set(LogoutSubscriber::class)\n -\u0026gt;tag('kernel.event_subscriber', [\n 'dispatcher' =\u0026gt; 'security.event_dispatcher.main',\n ]);\n };\n\u003c/pre\u003e\n\u003c/div\u003e\n\u003ca name=\"user-content-authentication-events\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAuthentication Events\u003c/h4\u003e\u003ca id=\"user-content-authentication-events\" class=\"anchor\" aria-label=\"Permalink: Authentication Events\" href=\"#authentication-events\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdl\u003e\n\u003cdt\u003e\u003ca href=\"#id117\"\u003e\u003cspan id=\"user-content-id118\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Http\\\\Event\\\\CheckPassportEvent`\u003c/span\u003e\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003eDispatched after the authenticator created the \u003ca href=\"#id119\"\u003e\u003cspan id=\"user-content-id120\"\u003e:ref:`security passport \u0026lt;security-passport\u0026gt;`\u003c/span\u003e\u003c/a\u003e.\nListeners of this event do the actual authentication checks (like\nchecking the passport, validating the CSRF token, etc.)\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#id121\"\u003e\u003cspan id=\"user-content-id122\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Http\\\\Event\\\\AuthenticationTokenCreatedEvent`\u003c/span\u003e\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003eDispatched after the passport was validated and the authenticator\ncreated the security token (and user). This can be used in advanced use-cases\nwhere you need to modify the created token (e.g. for multi factor\nauthentication).\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#id123\"\u003e\u003cspan id=\"user-content-id124\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Core\\\\Event\\\\AuthenticationSuccessEvent`\u003c/span\u003e\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003eDispatched when authentication is nearing success. This is the last\nevent that can make an authentication fail by throwing an\n\u003ccode\u003eAuthenticationException\u003c/code\u003e.\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#id125\"\u003e\u003cspan id=\"user-content-id126\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Http\\\\Event\\\\LoginSuccessEvent`\u003c/span\u003e\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003eDispatched after authentication was fully successful. Listeners to this\nevent can modify the response sent back to the user.\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#id127\"\u003e\u003cspan id=\"user-content-id128\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Http\\\\Event\\\\LoginFailureEvent`\u003c/span\u003e\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003eDispatched after an \u003ccode\u003eAuthenticationException\u003c/code\u003e was thrown during\nauthentication. Listeners to this event can modify the error response\nsent back to the user.\u003c/dd\u003e\n\u003c/dl\u003e\n\u003ca name=\"user-content-other-events\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eOther Events\u003c/h4\u003e\u003ca id=\"user-content-other-events\" class=\"anchor\" aria-label=\"Permalink: Other Events\" href=\"#other-events\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdl\u003e\n\u003cdt\u003e\u003ca href=\"#id129\"\u003e\u003cspan id=\"user-content-id130\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Http\\\\Event\\\\LogoutEvent`\u003c/span\u003e\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003eDispatched just before a user logs out of your application. See\n\u003ca href=\"#id131\"\u003e\u003cspan id=\"user-content-id132\"\u003e:ref:`security-logging-out`\u003c/span\u003e\u003c/a\u003e.\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#id133\"\u003e\u003cspan id=\"user-content-id134\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Http\\\\Event\\\\TokenDeauthenticatedEvent`\u003c/span\u003e\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003eDispatched when a user is deauthenticated, for instance because the\npassword was changed. See \u003ca href=\"#id135\"\u003e\u003cspan id=\"user-content-id136\"\u003e:ref:`user_session_refresh`\u003c/span\u003e\u003c/a\u003e.\u003c/dd\u003e\n\u003cdt\u003e\u003ca href=\"#id137\"\u003e\u003cspan id=\"user-content-id138\"\u003e:class:`Symfony\\\\Component\\\\Security\\\\Http\\\\Event\\\\SwitchUserEvent`\u003c/span\u003e\u003c/a\u003e\u003c/dt\u003e\n\u003cdd\u003eDispatched after impersonation is completed. See\n\u003ca href=\"#id139\"\u003e\u003cspan id=\"user-content-id140\"\u003e:doc:`/security/impersonating_user`\u003c/span\u003e\u003c/a\u003e.\u003c/dd\u003e\n\u003c/dl\u003e\n\u003ca name=\"user-content-frequently-asked-questions\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFrequently Asked Questions\u003c/h3\u003e\u003ca id=\"user-content-frequently-asked-questions\" class=\"anchor\" aria-label=\"Permalink: Frequently Asked Questions\" href=\"#frequently-asked-questions\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdl\u003e\n\u003cdt\u003e\u003cstrong\u003eCan I have Multiple Firewalls?\u003c/strong\u003e\u003c/dt\u003e\n\u003cdd\u003eYes! But it's usually not necessary. Each firewall is like a separate security\nsystem, being authenticated in one firewall doesn't make you authenticated in\nanother one. One firewall can have multiple diverse ways of allowing\nauthentication (e.g. form login, API key authentication and LDAP).\u003c/dd\u003e\n\u003cdt\u003e\u003cstrong\u003eCan I Share Authentication Between Firewalls?\u003c/strong\u003e\u003c/dt\u003e\n\u003cdd\u003eYes, but only with some configuration. If you're using multiple firewalls and\nyou authenticate against one firewall, you will \u003cem\u003enot\u003c/em\u003e be authenticated against\nany other firewalls automatically. Different firewalls are like different security\nsystems. To do this you have to explicitly specify the same\n\u003ca href=\"#id141\"\u003e\u003cspan id=\"user-content-id142\"\u003e:ref:`reference-security-firewall-context`\u003c/span\u003e\u003c/a\u003e for different firewalls. However,\none main firewall is usually sufficient for the needs of most applications.\u003c/dd\u003e\n\u003cdt\u003e\u003cstrong\u003eSecurity doesn't seem to work on my Error Pages\u003c/strong\u003e\u003c/dt\u003e\n\u003cdd\u003eAs routing is done \u003cem\u003ebefore\u003c/em\u003e security, 404 error pages are not covered by\nany firewall. This means you can't check for security or even access the\nuser object on these pages. See \u003ca href=\"#id143\"\u003e\u003cspan id=\"user-content-id144\"\u003e:doc:`/controller/error_pages`\u003c/span\u003e\u003c/a\u003e\nfor more details.\u003c/dd\u003e\n\u003cdt\u003e\u003cstrong\u003eMy Authentication Doesn't Seem to Work: No Errors, but I'm Never Logged In\u003c/strong\u003e\u003c/dt\u003e\n\u003cdd\u003eSometimes authentication may be successful, but after redirecting, you're\nlogged out immediately due to a problem loading the \u003ccode\u003eUser\u003c/code\u003e from the session.\nTo see if this is an issue, check your log file (\u003ccode\u003evar/log/dev.log\u003c/code\u003e) for\nthe log message:\u003c/dd\u003e\n\u003cdt\u003e\u003cstrong\u003eCannot refresh token because user has changed\u003c/strong\u003e\u003c/dt\u003e\n\u003cdd\u003eIf you see this, there are two possible causes. First, there may be a problem\nloading your User from the session. See \u003ca href=\"#id145\"\u003e\u003cspan id=\"user-content-id146\"\u003e:ref:`user_session_refresh`\u003c/span\u003e\u003c/a\u003e. Second,\nif certain user information was changed in the database since the last page\nrefresh, Symfony will purposely log out the user for security reasons.\u003c/dd\u003e\n\u003c/dl\u003e\n\u003ca name=\"user-content-learn-more\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eLearn More\u003c/h3\u003e\u003ca id=\"user-content-learn-more\" class=\"anchor\" aria-label=\"Permalink: Learn More\" href=\"#learn-more\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003ca name=\"user-content-authentication-identifying-logging-in-the-user\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAuthentication (Identifying/Logging in the User)\u003c/h4\u003e\u003ca id=\"user-content-authentication-identifyinglogging-in-the-user\" class=\"anchor\" aria-label=\"Permalink: Authentication (Identifying/Logging in the User)\" href=\"#authentication-identifyinglogging-in-the-user\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cpre\u003e.. toctree::\n :maxdepth: 1\n\n security/passwords\n security/ldap\n security/remember_me\n security/impersonating_user\n security/user_checkers\n security/firewall_restriction\n security/csrf\n security/form_login\n security/custom_authenticator\n security/entry_point\n\n\u003c/pre\u003e\n\u003ca name=\"user-content-authorization-denying-access\"\u003e\u003c/a\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAuthorization (Denying Access)\u003c/h4\u003e\u003ca id=\"user-content-authorization-denying-access\" class=\"anchor\" aria-label=\"Permalink: Authorization (Denying Access)\" href=\"#authorization-denying-access\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cpre\u003e.. toctree::\n :maxdepth: 1\n\n security/voters\n security/access_control\n security/expressions\n security/access_denied_handler\n security/force_https\n\n\u003c/pre\u003e\n\n\u003c/article\u003e","renderedFileInfo":null,"shortPath":null,"symbolsEnabled":true,"tabSize":8,"topBannersInfo":{"overridingGlobalFundingFile":false,"globalPreferredFundingPath":"/symfony/.github/blob/6f2ca452c856184a28812bb364b4e34ed50309da/FUNDING.yml","showInvalidCitationWarning":false,"citationHelpUrl":"https://docs.github.com/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-citation-files","actionsOnboardingTip":null},"truncated":false,"viewable":true,"workflowRedirectUrl":null,"symbols":null},"copilotInfo":null,"copilotAccessAllowed":false,"modelsAccessAllowed":false,"modelsRepoIntegrationEnabled":false,"csrf_tokens":{"/symfony/symfony-docs/branches":{"post":"SEOtNI-mFb-_HlgtVtOenuWgIdrUYXObnfek4maISnJMRB0W9bbYFW-zZI40Ptm_hA6F5vG6tdv6SSLA7EH67g"},"/repos/preferences":{"post":"ZmhWeQRXnVH4SvuULm4kNwQ3RUbBfqAwm86sQNaE25qmKQGmQR2KWszhLfkz3UW9Od9-Tuoj1LVzpL3KpWiKRQ"}}},"title":"symfony-docs/security.rst at cb6da5426b1e4dbf77ab6dc5978a3e4320c2331e · symfony/symfony-docs","appPayload":{"helpUrl":"https://docs.github.com","findFileWorkerPath":"/assets-cdn/worker/find-file-worker-263cab1760dd.js","findInFileWorkerPath":"/assets-cdn/worker/find-in-file-worker-1b17b3e7786a.js","githubDevUrl":null,"enabled_features":{"code_nav_ui_events":false,"react_blob_overlay":false,"accessible_code_button":true}}}