7

I provided two versions of the security.yaml file. The second version according to API Platform documentation. API Platform sends to the creation a custom user provider. For the second option security.yaml recommended at API Platform docs, I need to create two additional files. I did not attach them to the topic, but will do it if necessary.

But I think that problem it is in JWT.

Environment:

  • node v8.9.4
  • chrome 64.0.3282.119
  • Ubuntu 16.04
  • axios version: 0.16.2
  • Vue.js 2.4.2
  • vue-axios 2.0.2
  • api-platform/api-pack: 1.0
  • Symfony 4.0.4

User.php

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @ORM\Table(name="app_users")
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 */
class User implements UserInterface, \Serializable
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=25, unique=true)
     */
    private $username;

    /**
     * @ORM\Column(type="string", length=64)
     */
    private $password;

    /**
     * @ORM\Column(type="string", length=60, unique=true)
     */
    private $email;

    /**
     * @ORM\Column(name="is_active", type="boolean")
     */
    private $isActive;

    public function __construct() // add $username
    {
        $this->isActive = true;
    }

    public function getUsername()
    {
        return $this->username;
    }

    public function getSalt()
    {
        // you *may* need a real salt depending on your encoder
        // see section on salt below
        return null;
    }

    public function getPassword()
    {
        return $this->password;
    }

    public function getRoles()
    {
        return array('ROLE_ADMIN');
    }

    public function eraseCredentials()
    {
    }

    /** @see \Serializable::serialize() */
    public function serialize()
    {
        return serialize(array(
            $this->id,
            $this->username,
            $this->password,
            // see section on salt below
            // $this->salt,
        ));
    }

    /** @see \Serializable::unserialize() */
    public function unserialize($serialized)
    {
        list (
            $this->id,
            $this->username,
            $this->password,
            // see section on salt below
            // $this->salt
        ) = unserialize($serialized);
    }
}

First option security.yaml

security:

    encoders:
        App\Entity\User:
            algorithm: bcrypt

    providers:

        our_db_provider:
            entity:
                class: App\Entity\User
                property: username

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        login:
            pattern:  ^/api/login
            stateless: true
            anonymous: true
            form_login:
                check_path:               /api/login_check
                success_handler:          lexik_jwt_authentication.handler.authentication_success
                failure_handler:          lexik_jwt_authentication.handler.authentication_failure
                require_previous_session: false

        api:
            pattern:   ^/api
            stateless: true
            provider: our_db_provider
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator

    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }
        - { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api,       roles: IS_AUTHENTICATED_FULLY }

Second option security.yaml

security:

    encoders:
        App\Entity\User:
            algorithm: bcrypt

        App\Security\User\WebserviceUser: bcrypt

    providers:

        our_db_provider:
            entity:
                class: App\Entity\User
                property: username

        webservice:
            id: App\Security\User\WebserviceUserProvider

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        login:
            pattern:  ^/api/login
            stateless: true
            anonymous: true
            provider: webservice
            form_login:
                check_path:               /api/login_check
                success_handler:          lexik_jwt_authentication.handler.authentication_success
                failure_handler:          lexik_jwt_authentication.handler.authentication_failure
                require_previous_session: false

        api:
            pattern:   ^/api
            stateless: true
            provider: our_db_provider
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator
    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }
        - { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api,       roles: IS_AUTHENTICATED_FULLY }

Headers

headers

curl

curl

curl with headers

curl with headers

In browser

in browser

.env

###> lexik/jwt-authentication-bundle ###
# Key paths should be relative to the project directory 
JWT_PRIVATE_KEY_PATH=var/jwt/private.pem
JWT_PUBLIC_KEY_PATH=var/jwt/public.pem
JWT_PASSPHRASE=d70414362252a41ce772dff4823d084d
###< lexik/jwt-authentication-bundle ###

lexik_jwt_authentication.yaml

lexik_jwt_authentication:
    private_key_path: '%kernel.project_dir%/%env(JWT_PRIVATE_KEY_PATH)%'
    public_key_path:  '%kernel.project_dir%/%env(JWT_PUBLIC_KEY_PATH)%'
    pass_phrase:      '%env(JWT_PASSPHRASE)%'
jps
  • 20,041
  • 15
  • 75
  • 79
Dmitry S.
  • 3,766
  • 3
  • 18
  • 26

13 Answers13

18

My solutions was to add this in .htaccess

RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
cosy
  • 562
  • 1
  • 10
  • 20
  • 2
    Same here. More info: https://github.com/lexik/LexikJWTAuthenticationBundle/issues/451#issuecomment-443216804 – Jorr.it Dec 16 '18 at 08:46
  • I have the same problem, but the code you added is included now by default in new version, so I don't think your solution is the answer to the 401 error. – TaouBen Jul 27 '19 at 05:56
8

Problem be is encrypted private key.

Private key is normally encrypted and protected with a passphrase or password before the private key is transmitted or sent. When you receive an encrypted private key, you must decrypt the private key in order to use the private key.

To identify whether a private key is encrypted or not, open the private key in any text editor. An encrypted key has the first few lines that similar to the following, with the ENCRYPTED word:

---BEGIN RSA PRIVATE KEY---
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,AB8E2B5B2D989271273F6730B6F9C687
------
------
------
---END RSA PRIVATE KEY---

On the other hand, an unecrypted key will have the following format:

---BEGIN RSA PRIVATE KEY---
------
------
------
---END RSA PRIVATE KEY---

Encrypted key cannot be used directly in applications in most scenario. It must be decrypted first.

OpenSSL in Linux is the easiest way to decrypt an encrypted private key. Use the following command to decrypt an encrypted RSA key:

openssl rsa -in ssl.key.secure -out ssl.key

Make sure to replace the “server.key.secure” with the filename of your encrypted key, and “server.key” with the file name that you want for your encrypted output key file.

If the encrypted key is protected by a passphrase or password, enter the pass phrase when prompted.

Once done, you will notice that the ENCRYPTED wording in the file has gone.

If be I did not use Postman, then I would not have seen the error of Symfony, which helped me find the root of the problem. It would be nice if be Lesik LexikJWTAuthenticationBundle processed this error.

Dmitry S.
  • 3,766
  • 3
  • 18
  • 26
7

You need to allow Authorization header either in your project .htaccess file or virtual site configurations (example /etc/apache2/sites-available/000-default.conf)

<Directory your_project_directory>
            Options Indexes FollowSymLinks MultiViews
            AllowOverride None
            Order allow,deny
            Allow from all
            Require all granted
            RewriteEngine on
            RewriteCond %{HTTP:Authorization} ^(.*)
            RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
    </Directory>
Solomon Tesfaye
  • 186
  • 2
  • 10
6

To fix the issue, I added following line in my Apache configuration file.

SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

You can find the details in github LexikJWTAuthenticationBundle bottom of the page.

Krishna
  • 1,107
  • 1
  • 12
  • 10
4

I had the same problem, I fixed it by removing firewall login and merging it's content inside firewall api like this:

    api:
        pattern: ^/api
        stateless: true
        anonymous: true
        json_login:
            username_path: email
            check_path: /api/login_check
            success_handler: lexik_jwt_authentication.handler.authentication_success
            failure_handler: lexik_jwt_authentication.handler.authentication_failure
        guard:
            authenticators:
                - lexik_jwt_authentication.jwt_token_authenticator
hous
  • 2,577
  • 2
  • 27
  • 66
2

It works for me using this solution

RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
0

I was having trouble with this exact issue and my suggestion is to follow this steps to resolve yours:

  1. Obtain the token
  2. Generate the SSH keys : properly
  3. Send authenticate request using FormData

hope this will solve your problem.

habibun
  • 1,552
  • 2
  • 14
  • 29
  • Thanks for answer. I fulfilled all these steps, it is clear from the topic, but I get via curl `{"code":401,"message":"Bad credentials"}`. Also it is seen in review Headers that I get correct headers. – Dmitry S. Feb 07 '18 at 17:09
  • then check your provider: webservice carefully – habibun Feb 07 '18 at 17:16
  • The same provider works with the login form for the dashboard correctly: https://github.com/krypton-code/hastore-shop/blob/master/src/Entity/User.php – Dmitry S. Feb 07 '18 at 17:23
  • https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md#important-note-for-apache-users – habibun Feb 07 '18 at 17:28
  • I use nginx or you refer to on 'A stateless form_login replacement' or 'Impersonation'? – Dmitry S. Feb 07 '18 at 17:32
0

Try to regenerate private and public keys with custom passphrase and set it in .env file.

Change login firewall in security.yaml:

...
firewalls
...
    login:
        pattern:  ^/api/login
        stateless: true
        anonymous: true
        provider: our_db_provider
        json_login:
            check_path: /api/login_check
            username_path: username
            password_path: password
            success_handler: lexik_jwt_authentication.handler.authentication_success
            failure_handler: lexik_jwt_authentication.handler.authentication_failure
...

If it doesn't help, try to use FosUserBundle.

In composer.json add:

"friendsofsymfony/user-bundle": "dev-master"

In security.yaml:

...
providers:
...
    fos_userbundle:
        id: fos_user.user_provider.username
...
firewalls
...
    login:
        pattern:  ^/api/login
        stateless: true
        anonymous: true
        provider: fos_userbundle
        json_login:
            check_path: /api/login_check
            username_path: username
            password_path: password
            success_handler: lexik_jwt_authentication.handler.authentication_success
            failure_handler: lexik_jwt_authentication.handler.authentication_failure
...

See FOSUserBundle Integration in ApiPlatform docs

Zotov Egor
  • 11
  • 1
  • 5
  • Thanks for answer, but I tried already use these variants. The second option is not like as solution for me, need use `our_db_provider`. I want to understand the root of the problem. – Dmitry S. Feb 08 '18 at 17:17
  • If you wanna login with jwt you have to use **json_login** instead of **form_login**. Did you try to set your custom provider in login firewall rule? – Zotov Egor Feb 09 '18 at 04:17
  • Yes, I needed `our_db_provider` – Dmitry S. Feb 09 '18 at 14:58
  • Update issue on github, last comment: https://github.com/api-platform/api-platform/issues/557 – Dmitry S. Feb 09 '18 at 17:16
0

Besides other issues(and solutions) mentioned in answers I had one more issue, related to LexikJWTAuthenticationBundle. And Insomnia. When using Authorization tab in Insomnia and "Bearer Token", Insomnia is sending "authorization" header instead of "Authorization". Not sure if header should be case-sensitive or not, but LexikJWT is not working with "authorization", only with "Authorization".

Anton Kolenkov
  • 310
  • 4
  • 16
0

In this file (project/public/.htaccess) just add this:

SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
0

When sending your request make sure you are sending the content as JSON and not HTML.

Robert Saylor
  • 1,279
  • 9
  • 11
0

Answers below are not completed.

You need to create an .htaccess to /public folder and put this lines in section <IfModule mod_rewrite.c></IfModule> :

RewriteCond %{HTTP:Authorization} .
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

Full .htaccess give this for me and work :

# Use the front controller as index file. It serves as a fallback solution when
# every other rewrite/redirect fails (e.g. in an aliased environment without
# mod_rewrite). Additionally, this reduces the matching process for the
# start page (path "/") because otherwise Apache will apply the rewriting rules
# to each configured DirectoryIndex file (e.g. index.php, index.html, index.pl).
DirectoryIndex index.php

# By default, Apache does not evaluate symbolic links if you did not enable this
# feature in your server configuration. Uncomment the following line if you
# install assets as symlinks or if you experience problems related to symlinks
# when compiling LESS/Sass/CoffeScript assets.
# Options +FollowSymlinks

# Disabling MultiViews prevents unwanted negotiation, e.g. "/index" should not resolve
# to the front controller "/index.php" but be rewritten to "/index.php/index".
<IfModule mod_negotiation.c>
    Options -MultiViews
</IfModule>

<IfModule mod_rewrite.c>
    RewriteEngine On

    # Determine the RewriteBase automatically and set it as environment variable.
    # If you are using Apache aliases to do mass virtual hosting or installed the
    # project in a subdirectory, the base path will be prepended to allow proper
    # resolution of the index.php file and to redirect to the correct URI. It will
    # work in environments without path prefix as well, providing a safe, one-size
    # fits all solution. But as you do not need it in this case, you can comment
    # the following 2 lines to eliminate the overhead.
    RewriteCond %{REQUEST_URI}::$0 ^(/.+)/(.*)::\2$
    RewriteRule .* - [E=BASE:%1]

    # Sets the HTTP_AUTHORIZATION header removed by Apache
    #RewriteCond %{HTTP:Authorization} .+
    #RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]

RewriteCond %{HTTP:Authorization} .
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

    # Redirect to URI without front controller to prevent duplicate content
    # (with and without `/index.php`). Only do this redirect on the initial
    # rewrite by Apache and not on subsequent cycles. Otherwise we would get an
    # endless redirect loop (request -> rewrite to front controller ->
    # redirect -> request -> ...).
    # So in case you get a "too many redirects" error or you always get redirected
    # to the start page because your Apache does not expose the REDIRECT_STATUS
    # environment variable, you have 2 choices:
    # - disable this feature by commenting the following 2 lines or
    # - use Apache >= 2.3.9 and replace all L flags by END flags and remove the
    #   following RewriteCond (best solution)
    RewriteCond %{ENV:REDIRECT_STATUS} =""
    RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]

    # If the requested filename exists, simply serve it.
    # We only want to let Apache serve files and not directories.
    # Rewrite all other queries to the front controller.
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>

<IfModule !mod_rewrite.c>
    <IfModule mod_alias.c>
        # When mod_rewrite is not available, we instruct a temporary redirect of
        # the start page to the front controller explicitly so that the website
        # and the generated links can still be used.
        RedirectMatch 307 ^/$ /index.php/
        # RedirectTemp cannot be used instead
</IfModule>
</IfModule>
Dharman
  • 30,962
  • 25
  • 85
  • 135
Joffrey Outtier
  • 880
  • 10
  • 9
0

For Symfony 5.3 and higher anonymous: true attribute has been removed from security.yaml file,

I fixed my probem by replacing it like this:

access_control:
    - { path: ^/api/login, roles: PUBLIC_ACCESS }
    - { path: ^/api,       roles: IS_AUTHENTICATED_FULLY }
Khribi Wessim
  • 287
  • 2
  • 12