.. _ch-security: ======== Security ======== .. sectionauthor:: Andreas Förthner, Bastian Waidelich Security Framework ================== All tasks related to security of a Flow application are handled centrally by the security framework. Besides other functionality, this includes especially features like authentication, authorization, channel security and a powerful policy component. This chapter describes how you can use Flow's security features and how they work internally. Security context ---------------- The :abbr:`Security Context (\\Neos\\Flow\\Security\\Context)` is initialized as soon as an HTTP request is being dispatched. It lies in session scope and holds context data like the current authentication status. That means, if you need data related to security, the security context (you can get it easily with dependency injection) will be your main information source. The details of the context's data will be described in the next chapters. Authentication ============== One of the main things people associate with security is authentication. That means to identify your communication partner - the one sending a request to Flow. Therefore the framework provides an infrastructure to easily use different mechanisms for such a plausibility proof. The most important achievement of the provided infrastructure is its flexible extensibility. You can easily write your own authentication mechanisms and configure the framework to use them without touching the framework code itself. The details are explained in the section :ref:`Implementing your own authentication mechanism`. .. _Using the authentication controller: Using the authentication controller ----------------------------------- First, let's see how you can use Flow's authentication features. There is a base controller in the security package: the :abbr:`AbstractAuthenticationController (\\Neos\\Flow\\Security\\Authentication\\Controller\\AbstractAuthenticationController)`, which already contains almost everything you need to authenticate an account. This controller has three actions, namely ``loginAction()``, ``authenticateAction()`` and ``logoutAction()``. To use authentication in your project you have to inherit from this controller, provide a template for the login action (e.g. a login form) and implement at least the abstract method ``onAuthenticationSuccess()``. This method is called if authentication succeeded and will be passed the intercepted request, which triggered authentication. This can be used to resume the original request in order to send the user to the protected area he had tried to access. You may also want to override ``onAuthenticationFailure()`` to react on login problems appropriately. *Example: Simple authentication controller* :: redirectToRequest($originalRequest); } $this->redirect('someDefaultActionAfterLogin'); } /** * Logs all active tokens out and redirects the user to the login form * * @return void */ public function logoutAction() { parent::logoutAction(); $this->addFlashMessage('Logout successful'); $this->redirect('index'); } } The mechanism that is eventually used to authenticate is implemented in a so called authentication provider. The most common provider (``PersistedUsernamePasswordProvider``) authenticates a user account by checking a username and password against accounts stored in the database. [#]_ *Example: Configuration of a username/password authentication mechanism in Settings.yaml* .. code-block:: yaml Neos: Flow: security: authentication: providers: 'SomeAuthenticationProvider': provider: 'PersistedUsernamePasswordProvider' This registers the :abbr:`PersistedUsernamePasswordProvider (\\Neos\\Flow\\Security\\Authentication\\Provider\\PersistedUsernamePasswordProvider)` authentication provider under the name "``SomeAuthenticationProvider``" as the only, global authentication mechanism. To successfully authenticate an account with this provider, you'll obviously have to provide a username and password. This is done by sending two POST variables to the authentication controller. Given there is a route that resolves “your/app/authenticate” to the ``authenticateAction()`` of the custom ``AuthenticationController``, users can be authenticated with a simple login form like the following: *Example: A simple login form* .. code-block:: html
After submitting the form the internal authentication process will be triggered and if the provided credentials are valid an account will be authenticated afterwards. [#]_ The internal workings of the authentication process --------------------------------------------------- Now that you know, how you can authenticate, let's have a look at the internal process. The following sequence diagram shows the participating components and their interaction: .. figure:: Images/Security_BasicAuthenticationProcess.png :alt: Internal authentication process :class: screenshot-fullsize Internal authentication process As already explained, the security framework is initialized in the ``Neos\Flow\Mvc\Dispatcher``. It intercepts the request dispatching before any controller is called. Regarding authentication, you can see, that a so called authentication token will be stored in the security context and some credentials will be updated in it. Authentication tokens ~~~~~~~~~~~~~~~~~~~~~ An authentication token holds the status of a specific authentication mechanism, for example it receives the credentials (e.g. a username and password) needed for authentication and stores one of the following authentication states in the session. [#]_ These constants are defined in the authentication token interface (``Neos\Flow\Security\Authentication\TokenInterface``) and the status can be obtained from the ``getAuthenticationStatus()`` method of any token. .. tip:: If you only want to know, if authentication was successful, you can call the convenience method ``isAuthenticated()``. ``NO_CREDENTIALS_GIVEN`` This is the default state. The token is not authenticated and holds no credentials, that could be used for authentication. ``WRONG_CREDENTIALS`` It was tried to authenticate the token, but the credentials were wrong. ``AUTHENTICATION_SUCCESSFUL`` The token has been successfully authenticated. ``AUTHENTICATION_NEEDED`` This indicates, that the token received credentials, but has not been authenticated yet. Now you might ask yourself, how a token receives its credentials. The simple answer is: It's up to the token, to fetch them from somewhere. The ``UsernamePassword`` token for example checks for a username and password in POST parameters: By default those parameters are ``__authentication[Neos][Flow][Security][Authentication][Token][UsernamePassword][username]`` and ``__authentication[Neos][Flow][Security][Authentication][Token][UsernamePassword][password]``. This can be changed via ``tokenOptions``: .. code-block:: yaml Neos: Flow: security: authentication: providers: 'SomeAuthenticationProvider': provider: 'PersistedUsernamePasswordProvider' tokenOptions: usernamePostField: 'auth.username' passwordPostField: 'auth.password' With that, the ``auth[username]`` & ``auth[password]`` parameters would be evaluated instead – The login template has to be adjusted accordingly of course (see :ref:`Using the authentication controller`). The framework only makes sure that ``updateCredentials()`` is called on every token, then the token has to set possibly available credentials itself, e.g. from available headers or parameters or anything else you can provide credentials with. Flow ships with the following authentication tokens: #. ``UsernamePassword``: Extracts username & password from a POST parameter. Options: ``usernamePostField`` and ``passwordPostField`` #. ``UsernamePasswordHttpBasic``: Extracts username & password from the the ``Authorization`` header (Basic auth). This token is sessionless (see below) #. ``BearerToken``: Extracts a rfc6750 bearer token (See: `https://tools.ietf.org/html/rfc6750`_) from a given ``Authorization`` header. This token is sessionless (see below) and has no configuration options. #. ``PasswordToken``: Extracts password from a POST parameter. Options: ``passwordPostField`` But it's really easy to create additional tokens: Custom authentication tokens ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Any class that implements the ``TokenInterface`` can be used as an authentication token. The following example implements a token that extracts the ``Bearer`` from an ``Authentication`` header: *Example: BearerToken.php* :: getHttpRequest()->getHeaderLine('Authorization'); if (strncmp($authorizationHeader, 'Bearer ', 7) !== 0) { $this->credentials['bearer'] = null; $this->authenticationStatus = self::NO_CREDENTIALS_GIVEN; return; } $this->credentials['bearer'] = substr($authorizationHeader, 7); $this->authenticationStatus = self::AUTHENTICATION_NEEDED; } } In order to make use of this token, the fully qualified class name has to be specified in the ``token`` configuration of the corresponding provider: .. code-block:: yaml Neos: Flow: security: authentication: providers: 'SomeAuthenticationProvider': provider: # ... token: 'Acme\YourPackage\BearerToken' .. tip:: Since the Bearer authentication header is expected to be sent with every request, this token also implements the ``SessionlessTokenInterface`` (see next section). For the above token a custom Provider is needed, since the built-in providers won't know how to deal with the ``Bearer`` token. If a custom token represents username and/or password credentials, they can implement the ``UsernamePasswordTokenInterface`` or ``PasswordTokenInterface``. That way they can be used with the existing ``PersistedUsernamePasswordProvider`` or ``FileBasedSimpleKeyProvider``. The following example implements a custom token that extracts username and token from a HTTP header that can be specified via options: *Example: UsernamePasswordFromHeaderToken.php* :: usernameHeaderName = $options['usernameHeaderName'] ?? 'X-Username'; $this->passwordHeaderName = $options['passwordHeaderName'] ?? 'X-Password'; } public function updateCredentials(ActionRequest $actionRequest) { $username = $actionRequest->getHttpRequest()->getHeaderLine($this->usernameHeaderName); $password = $actionRequest->getHttpRequest()->getHeaderLine($this->passwordHeaderName); if (empty($username) || empty($password)) { $this->credentials = ['username' => null, 'password' => null]; $this->authenticationStatus = self::NO_CREDENTIALS_GIVEN; return; } $this->credentials = ['username' => $username, 'password' => $password]; $this->authenticationStatus = self::AUTHENTICATION_NEEDED; } public function getUsername(): string { return $this->credentials['username'] ?? ''; } public function getPassword(): string { return $this->credentials['password'] ?? ''; } } This would read username & password from ``X-Username`` and ``X-Password`` headers by default, but allow the header names to be specified via ``tokenOptions``. .. code-block:: yaml Neos: Flow: security: authentication: providers: DefaultProvider: provider: PersistedUsernamePasswordProvider token: 'Acme\YourPackage\UsernamePasswordFromHeaderToken' tokenOptions: usernameHeaderName: 'Some-Header' passwordHeaderName: 'Some-Other-Header' The ``tokenOptions`` will be passed to the constructor of the token. .. note:: This serves merely as a simple example of the extension mechanism. It's probably not a good idea to specify credentials via arbitrary HTTP headers! Sessionless authentication tokens ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default Flow assumes that a token which has been successfully authenticated needs a session in order to keep being authenticated on the next HTTP request. Therefore, whenever a user sends a ``UsernamePassword`` token for authentication, Flow will implicitly start a session and send a session cookie. For authentication mechanisms which don't require a session this process can be optimized. Headers for HTTP Basic Authentication or an API key is sent on every request, so there's no need to start a session for keeping the token. Especially when dealing with REST services, it is not desirable to start a session. Authentication tokens which don't require a session simply need to implement the :abbr:`SessionlessTokenInterface (\\Neos\\Flow\\Security\\Authentication\\Token\\SessionlessTokenInterface)` marker interface. If a token carries this marker, the Authentication Manager will refrain from starting a session during authentication. Authentication manager and provider ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ After the tokens have been initialized the original request will be processed by the resolved controller. Usually this is done by your authentication controller inheriting the AbstractAuthenticationController of Flow, which will call the authentication manager to authenticate the tokens. In turn the authentication manager calls all authentication providers in the configured order. A provider implements a specific authentication mechanism and is therefore responsible for a specific token type. E.g. the already mentioned ``PersistedUsernamePasswordProvider`` provider is able to authenticate the ``UsernamePassword`` token. After checking the credentials, it is the responsibility of an authentication provider to set the correct authentication status (see above) and ``Roles`` in its corresponding token. The role implementation resides in the ``Neos\Flow\Security\Policy`` namespace. (see the Policy section for details). .. _Account management: Account management ------------------ In the previous section you have seen, how accounts can be authenticated in Flow. What was concealed so far is, how these accounts are created or what is exactly meant by the word "account". First of all let's define what accounts are in Flow and how they are used for authentication. Following the OASIS CIQ V3.0 [#]_ specification, an account used for authentication is separated from a user or more general a party. The advantage of this separation is the possibility of one user having more than one account. E.g. a user could have an account for the ``UsernamePassword`` provider and one account connected to an LDAP authentication provider. Another scenario would be to have different accounts for different parts of your Flow application. Read the next section :ref:`Advanced authentication configuration` to see how this can be accomplished. As explained above, the account stores the credentials needed for authentication. Obviously these credentials are provider specific and therefore every account is only valid for a specific authentication provider. This provider to account connection is stored in a property of the account object named ``authenticationProviderName``. Appropriate getters and setters are provided. The provider name is configured in the *Settings.yaml* file. If you look back to the default configuration, you'll find the name of the default authentication provider: ``DefaultProvider``. Besides that, each account has another property called ``credentialsSource``, which points to the place or describes the credentials needed for this account. This could be an LDAP query string, or in case of the ``PersistedUsernamePasswordProvider``, the username, password hash and salt are stored directly in this member variable. It is the responsibility of the authentication provider to check the given credentials from the authentication token, find the correct account for them [#]_ and to decide about the authentication status of this token. .. note:: In case of a directory service, the real authentication will probably not take place in the provider itself, but the provider will pass the result of the directory service on to the authentication token. .. note:: The ``DefaultProvider`` authentication provider used in the examples is not shipped with Flow, you have to configure all available authentication providers in your application. Creating accounts ~~~~~~~~~~~~~~~~~ Creating an account is as easy as creating a new account object and add it to the account repository. Look at the following example, which uses the ``Neos\Flow\Security\AccountFactory`` to create a simple username/password account for the DefaultProvider: *Example: Add a new username/password account* :: $identifier = 'andi'; $password = 'secret'; $roles = array('Acme.MyPackage:Administrator'); $authenticationProviderName = 'DefaultProvider'; $account = $this->accountFactory->createAccountWithPassword($identifier, $password, $roles, $authenticationProviderName); $this->accountRepository->add($account); The way the credentials are stored internally is completely up to the authentication provider. The ``PersistedUsernamePasswordProvider`` uses the ``Neos\Flow\Security\Cryptography\HashService`` to verify the given password. In the example above, the given plaintext password will be securely hashed by the ``HashService``. The hashing is the main magic happening in the ``AccountFactory`` and the reason why we don't create the account object directly. If you want to learn more about secure password hashing in Flow, you should read the section about :ref:`Cryptography` below. You can also see, that there is an array of roles added to the account. This is used by the policy system and will be explained in the according section below. .. note:: This example expects the account factory and account repository to be available in ``$this->accountFactory`` and ``$this->accountRepository`` respectively. If you use this snippet in a command controller, these can be injected very easily by dependency injection. .. _Advanced authentication configuration: Advanced authentication configuration ------------------------------------- Parallel authentication ~~~~~~~~~~~~~~~~~~~~~~~ Now that you have seen all components, taking part in the authentication process, it is time to have a look at some advanced configuration possibilities. Just to remember, here is again the configuration of an authentication provider: .. code-block:: yaml security: authentication: providers: 'DefaultProvider': provider: 'PersistedUsernamePasswordProvider' If you have a closer look at this configuration, you can see, that the word providers is plural. That means, you have the possibility to configure more than one provider and use them in "parallel". .. note:: You will have to make sure, that each provider has a unique name. In the example above the provider name is ``DefaultProvider``. .. note:: You can also disable an authentication provider by setting the provider value to ``false`` in the YAML configuration. For instance ``DefaultProvider: false``. *Example: Configuration of two authentication providers* .. code-block:: yaml security: authentication: providers: 'MyLDAPProvider': provider: 'Neos\MyCoolPackage\Security\Authentication\MyLDAPProvider' providerOptions: 'Some LDAP configuration options' 'DefaultProvider': provider: 'PersistedUsernamePasswordProvider' This will advice the authentication manager to first authenticate over the LDAP provider and if that fails it will try to authenticate the default provider. So this configuration can be seen as an authentication fallback chain, of course you can configure as many providers as you like, but keep in mind that the order matters. .. note:: As you can see in the example, the LDAP provider is provided with some options. These are specific configuration options for each provider, have a look in the detailed description to know if a specific provider needs more options to be configured and which. Parallel authentication for the same account ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Accounts are bound to an authentication provider and by default the ``PersistedUsernamePasswordProvider`` will only lookup accounts that belong to the provider (i.e. with an ``authenticationProviderName`` that is equal to the configured provider name, ``SomeAuthenticationProvider`` in this example. That lookup name can be changed via the ``lookupProviderName`` option that allows the provider to lookup accounts for a different configuration. This can be useful in order to re-use the same provider & accounts for multiple authentication types, for example classic form-based and HTTP basic auth: .. code-block:: yaml Neos: Flow: security: authentication: providers: 'Acme.SomePackage:Default': requestPatterns: # ... provider: PersistedUsernamePasswordProvider token: UsernamePassword entryPoint: WebRedirect entryPointOptions: routeValues: '@package': Acme.SomePackage '@controller': Authentication '@action': login 'Acme.SomePackage:Default.HttpBasic': requestPatterns: # ... provider: PersistedUsernamePasswordProvider providerOptions: lookupProviderName: 'Acme.SomePackage:Default' token: UsernamePasswordHttpBasic entryPoint: HttpBasic Multi-factor authentication strategy ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There is another configuration option to realize a multi-factor-authentication. It defaults to ``oneToken``. A configurable authentication strategy of ``allTokens`` forces the authentication manager to always authenticate all configured providers and to make sure that every single provider returned a positive authentication status to one of its tokens. The authentication strategy ``atLeastOneToken`` will try to authenticate as many tokens as possible but at least one. This is helpful to realize policies with additional security only for some resources (e.g. SSL client certificates for an admin backend). .. code-block:: yaml configuration: security: authentication: authenticationStrategy: allTokens Reuse of tokens and providers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There is another configuration option for authentication providers called ``token``, which can be specified in the provider settings. By this option you can specify which token should be used for a provider. Remember the token is responsible for the credentials retrieval, i.e. if you want to authenticate let's say via username and password this setting enables to to specify where these credentials come from. So e.g. you could reuse the one username/password provider class and specify, whether authentication credentials are sent in a POST request or set in an HTTP Basic authentication header. *Example: Specifying a specific token type for an authentication provider* .. code-block:: yaml security: authentication: providers: 'DefaultProvider': provider: 'PersistedUsernamePasswordProvider' token: 'UsernamePasswordHttpBasic' .. _Request Patterns: Request Patterns ~~~~~~~~~~~~~~~~ Now that you know about the possibility of configuring more than one authentication provider another scenario may come to your mind. Just imagine an application with two areas: One user area and one administration area. Both must be protected, so we need some kind of authentication. However for the administration area we want a stronger authentication mechanism than for the user area. Have a look at the following provider configuration: *Example: Using request patterns* .. code-block:: yaml security: authentication: providers: 'LocalNetworkProvider': provider: 'FileBasedSimpleKeyProvider' providerOptions: keyName: 'AdminKey' authenticateRoles: ['Acme.SomePackage:Administrator'] requestPatterns: 'Acme.SomePackage:AdministrationArea': pattern: 'ControllerObjectName' patternOptions: 'controllerObjectNamePattern': 'Acme\SomePackage\AdministrationArea\.*' 'Acme.SomePackage:LocalNetwork': pattern: 'Ip' patternOptions: 'cidrPattern': '192.168.178.0/24' 'MyLDAPProvider': provider: 'Neos\MyCoolPackage\Security\Authentication\MyLDAPProvider' providerOptions: 'Some LDAP configuration options' requestPatterns: 'Acme.SomePackage:AdministrationArea': pattern: 'ControllerObjectName' patternOptions: 'controllerObjectNamePattern': 'Acme\SomePackage\AdministrationArea\.*' DefaultProvider: provider: 'PersistedUsernamePasswordProvider' requestPatterns: 'Acme.SomePackage:UserArea': pattern: 'ControllerObjectName' patternOptions: 'controllerObjectNamePattern': 'Acme\SomePackage\UserArea\.*' Look at the new configuration option ``requestPatterns``. This enables or disables an authentication provider, depending on given patterns. The patterns will look into the data of the current request and tell the authentication system, if they match or not. The patterns in the example above will match, if the controller object name of the current request (the controller to be called) matches on the given regular expression. If a pattern does not match, the corresponding provider will be ignored in the whole authentication process. In the above scenario this means, all controllers responsible for the administration area will use the LDAP authentication provider unless the user is on the internal network, in which case he can use a simple password. The user area controllers will be authenticated by the default username/password provider. .. note:: You can use more than one pattern in the configuration. Then the provider will only be active, if all patterns match on the current request. .. tip:: There can be patterns that match on different data of the request. Just imagine an IP pattern, that matches on the request IP. You could, e.g. provide different authentication mechanisms for people coming from your internal network, than for requests coming from the outside. .. tip:: You can easily implement your own pattern. Just implement the interface ``Neos\Flow\Security\RequestPatternInterface`` and configure the pattern with its full qualified class name. :title:`Available request patterns` +----------------------+-----------------------------------------------+------------------------------------------+------------------------------------------------------------------+ | Request Pattern | Match criteria | Configuration options | Description | +======================+===============================================+==========================================+==================================================================+ | ControllerObjectName | Matches on the object name of the controller | ``controllerObjectNamePattern`` | A regular expression to match on the object name, for example: | | | that has been resolved by the MVC dispatcher | | | | | for the current request | | ``controllerObjectNamePattern: 'My\Package\Controller\Admin\.*`` | +----------------------+-----------------------------------------------+------------------------------------------+------------------------------------------------------------------+ | Uri | Matches on the URI of the current request | ``uriPattern`` | A regular expression to match on the URI, for example: | | | of the current request | | | | | | | ``uriPattern: '/admin/.*`` | +----------------------+-----------------------------------------------+------------------------------------------+------------------------------------------------------------------+ | Host | Matches on the host part of the current | ``hostPattern`` | A wildcard expression to match on the hostname, for example: | | | request | | | | | | | ``hostPattern: '*.mydomain.com'`` or | | | | | ``hostPattern: 'www.mydomain.*'`` | +----------------------+-----------------------------------------------+------------------------------------------+------------------------------------------------------------------+ | Ip | Matches on the user IP address of the current | ``cidrPattern`` | A CIDR expression to match on the source IP, for example: | | | request | | | | | | | ``cidrPattern: '192.168.178.0/24'`` or | | | | | ``cidrPattern: 'fd9e:21a7:a92c:2323::/96'`` | +----------------------+-----------------------------------------------+------------------------------------------+------------------------------------------------------------------+ .. note:: The pattern for ``Uri`` will have slashes escaped and is amended with ``^…$`` automatically, so do not include those in your pattern! Authentication entry points --------------------------- One question that has not been answered so far is: what happens if the authentication process fails? In this case the authentication manager will throw an ``AuthenticationRequired`` exception. It might not be the best idea to let this exception settle its way up to the browser, right? Therefore we introduced a concept called authentication entry points. These entry points catch the mentioned exception and should redirect the user to a place where she can provide proper credentials. This could be a login page for the username/password provider or an HTTP header for HTTP authentication. An entry point can be configured for each authentication provider. Look at the following example, that redirects to a login page (Using the ``WebRedirect`` entry point). *Example: Redirect an ``AuthenticationRequired`` exception to the login page* .. code-block:: yaml security: authentication: providers: DefaultProvider: provider: PersistedUsernamePasswordProvider entryPoint: 'WebRedirect' entryPointOptions: routeValues: '@package': 'Your.Package' '@controller': 'Authenticate' '@action': 'login' .. note:: Prior to Flow version 1.2 the option ``routeValues`` was not supported by the WebRedirect entry point. Instead you could provide the option ``uri`` containing a relative or absolute URI to redirect to. This is still possible, but we recommend to use ``routeValues`` in order to make your configuration more independent from the routing configuration. .. note:: Of course you can implement your own entry point and configure it by using its full qualified class name. Just make sure to implement the ``Neos\Flow\Security\Authentication\EntryPointInterface`` interface. .. tip:: If a request has been intercepted by an ``AuthenticationRequired`` exception, this request will be stored in the security context. By this, the authentication process can resume this request afterwards. Have a look at the Flow authentication controller if you want to see this feature in action. :title:`Available authentication entry points` +--------------+---------------------------+---------------------------------------------+ | Entry Point | Description | Configuration options | +==============+===========================+=============================================+ | WebRedirect | Triggers an HTTP redirect | Expects an associative array with | | | to a given uri or action. | either an entry ``uri`` (obsolete, see Note | | | | above), or an array ``routeValues``; for | | | | example:: | | | | | | | | uri: login/ | | | | | | | | or :: | | | | | | | | routeValues: | | | | '@package': 'Your.Package' | | | | '@controller': 'Authenticate' | | | | '@action': 'login' | +--------------+---------------------------+---------------------------------------------+ | HttpBasic | Adds a WWW-Authenticate | Optionally takes an option realm, which | | | header to the response, | will be displayed in the authentication | | | which will trigger the | prompt. | | | browsers authentication | | | | form. | | +--------------+---------------------------+---------------------------------------------+ .. _Authentication mechanisms shipped with Flow: Authentication mechanisms shipped with Flow ------------------------------------------- This section explains the details of each authentication mechanism shipped with Flow. Mainly the configuration options and usage will be exposed, if you want to know more about the entire authentication process and how the components will work together, please have a look in the previous sections. Simple username/password authentication ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *Provider* The implementation of the corresponding authentication provider resides in the class ``Neos\Flow\Security\Authentication\Provider\PersistedUsernamePasswordProvider``. It is able to authenticate tokens of the type ``Neos\Flow\Security\Authentication\Token\UsernamePassword``. It expects a credentials array in the token which looks like that:: array( 'username' => 'admin', 'password' => 'plaintextPassword' ); It will try to find an account in the ``Neos\Flow\Security\AccountRepository`` that has the username value as account identifier and fetch the credentials source. .. tip:: You should always use the Flow hash service to generate hashes! This will make sure that you really have secure hashes. The provider will try to authenticate the token by asking the Flow hash service to verify the hashed password against the given plaintext password from the token. If you want to know more about accounts and how you can create them, look in the corresponding section above. *Token* The username/password token is implemented in the class ``Neos\Flow\Security\Authentication\Token\UsernamePassword``. It fetches the credentials from the HTTP POST data, look at the following program listing for details:: $postArguments = $this->environment->getRawPostArguments(); $username = \Neos\Utility\ObjectAccess::getPropertyPath($postArguments, '__authentication.Neos.Flow.Security.Authentication.Token.UsernamePassword.username'); $password = \Neos\Utility\ObjectAccess::getPropertyPath($postArguments, '__authentication.Neos.Flow.Security.Authentication.Token.UsernamePassword.password'); .. note:: The token expects a plaintext password in the POST data. That does not mean, you have to transfer plaintext passwords, however it is not the responsibility of the authentication layer to encrypt the transfer channel. Look in the section about :ref:`Channel security` for any details. .. _Implementing your own authentication mechanism: Implementing your own authentication mechanism ---------------------------------------------- One of the main goals for the authentication architecture was to provide an easily extensible infrastructure. Now that the authentication process has been explained, you'll here find the steps needed to implement your own authentication mechanism: *Authentication token* You'll have to provide an authentication token, that implements the interface ``Neos\Flow\Security\Authentication\TokenInterface``: #. The most interesting method is ``updateCredentials()``. There you'll get the current request and you'll have to make sure that credentials sent from the client will be fetched and stored in the token. #. Implement the remaining methods of the interface. These are mostly getters and setters, have a look in one of the existing tokens (for example ``Neos\Flow\Security\Authentication\Token\UsernamePassword``), if you need more information. .. tip:: You can inherit from the ``AbstractToken`` class, which will most likely have a lot of the methods already implemented in a way you need them. *Authentication provider* After that you'll have to implement your own authentication mechanism by providing a class, that implements the interface ``Neos\Flow\Security\Authentication\AuthenticationProviderInterface``: #. In the constructor you will get the name, that has been configured for the provider and an optional options array. Basically you can decide on your own which options you need and how the corresponding yaml configuration will look like. #. Then there has to be a ``canAuthenticate()`` method, which gets an authentication token and returns a boolean value whether your provider can authenticate that token or not. Most likely you will call ``getAuthenticationProviderName()`` on the token and check, if it matches the provider name given to you in your provider's constructor. In addition to this, the method ``getTokenClassNames()`` has to return an array with all authentication token classes, your provider is able to authenticate. #. All the magic will happen in the ``authenticate()`` method, which will get an appropriate authentication token. Basically you could do whatever you want in this method, the only thing you'll have to make sure is to set the correct status (possible values are defined as constants in the token interface and explained above). If authentication succeeds you might also want to set an account in the given token, to add some roles to the current security context. However, here is the recommended way of what should be done in this method and if you don't have really good reasons, you shouldn't deviate from this procedure. #. Get the credentials provided by the client from the authentication token (``getCredentials()``) #. Retrieve the corresponding account object from the account repository, which you should inject into your provider by dependency injection. The repository provides a convenient find method for this task: ``findActiveByAccountIdentifierAndAuthenticationProviderName()``. #. The ``credentialsSource`` property of the account will hold the credentials you'll need to compare or at least the information, where these credentials lie. #. Start the authentication process (e.g. compare credentials/call directory service/...). #. Depending on the authentication result, set the correct status in the authentication token, by ``calling setAuthenticationStatus()``. #. Set the account in the authentication token, if authentication succeeded. This will add the roles of this token to the security context. .. tip:: You can inherit from the ``AbstractProvider`` class, which will most likely have a lot of the methods already implemented in a way you need them. Authorization ============= This section covers the authorization features of Flow and how those can be leveraged in order to configure fine grained access rights. Privileges ---------- In a complex web application there are different elements you might want to protect. This could be the permission to execute certain actions or the retrieval of certain data that has been stored in the system. In order to distinguish between the different types the concept of ``Privilege Types`` has been introduced. Privilege Types are responsible to protect the different parts of an application. Flow provides the two generic types ``MethodPrivilege`` and ``EntityPrivilege``, which will be explained in detail in the sections below. .. _Access Control Lists: Defining Privileges (Policies) ============================== This section will introduce the recommended and default way of connecting authentication with authorization. In Flow policies are defined in a declarative way. This is very powerful and gives you the possibility to change the security policy of your application without touching any PHP code. The policy system deals with two major objects, which are explained below: ``Roles`` and ``Privilege Targets``. All policy definitions are configured in the ``Policy.yaml`` files. *Privilege Targets* In general a Privilege Target is the definition pointing to something you want to protect. It consists of a **Privilege Type**, a **unique name**, an optional human readable **label** and a **matcher expression** defining which things should be protected by this target. The privilege type defines the nature of the element to protect. This could be the execution of a certain action in your system, the retrieval of objects from the database, or any other kind of action you want to supervise in your application. The following example defines a Privilege Target for the ``MethodPrivilege`` type to protect the execution of some methods. *Example: privilege target definition in the Policy.yaml file* .. code-block:: yaml privilegeTargets: 'Neos\Flow\Security\Authorization\Privilege\Method\MethodPrivilege': 'Acme.MyPackage:RestrictedController.customerAction': label: 'Optional label to describe this privilege target' matcher: 'method(Acme\MyPackage\Controller\RestrictedController->customerAction())' 'Acme.MyPackage:RestrictedController.adminAction': matcher: 'method(Acme\MyPackage\Controller\RestrictedController->adminAction())' 'Acme.MyPackage:editOwnPost': matcher: 'method(Acme\MyPackage\Controller\PostController->editAction(post.owner == current.userService.currentUser))' .. note: The label will be rendered by the ``./flow security:describeRole