lexik / LexikJWTAuthenticationBundle

JWT authentication for your Symfony API
MIT License
2.52k stars 610 forks source link

[RESOLVED] CURL works - AngularJS get a 401 Bad Credentials #102

Closed Morgiver closed 8 years ago

Morgiver commented 8 years ago

Hi,

I'm trying to use the bundle, but i'm blocked when i want to post credentials with AngularJS.

The server side working fine, when i'm using the curl command, it's return the token. But when i'm using the $http.post() function in AngularJS, it's returning 401 Bad Credentials response.

Here's my code on Client side :

function loginCheck(data) {
        var defer = $q.defer();

        $http.post('api/login_check', data, { ignoreAuthModule: true }).then(function(success) {
            defer.resolve(success);
        }, function(error) {
            defer.reject(error);
        });

        return defer.promise;
}

Here's my security.yml

# app/config/security.yml
security:
    encoders:
        Symfony\Component\Security\Core\User\User: plaintext

    providers:
        in_memory:
            memory:
                users:
                    user:
                        password: user
                        roles: 'ROLE_USER'
                    admin:
                        password: admin
                        roles: 'ROLE_ADMIN'

    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
                username_parameter:       _username
                password_parameter:       _password
                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
            lexik_jwt: ~

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

    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

Did i miss something ?

Thx in advance for help :)

Morgiver

slashfan commented 8 years ago

Hi, can you copy the exact request made by your angular code (from the network tab in the chrome console for example) ?

Morgiver commented 8 years ago

Hi, sorry, wasn't able to be on the pc.

So, the request made by AngularJS is the following one :

Remote Address:127.0.0.1:80
Request URL:http://127.0.0.1/ek-php/workspace/web/app_dev.php/api/login_check
Request Method:POST
Status Code:401 Unauthorized
Response Headers
view source
Cache-Control:no-cache
Connection:Keep-Alive
Content-Length:40
Content-Type:application/json
Date:Thu, 22 Oct 2015 07:00:20 GMT
Keep-Alive:timeout=5, max=100
Server:Apache/2.4.4 (Win32) PHP/5.4.14
X-Debug-Token:8ad8b2
X-Debug-Token-Link:/ek-php/workspace/web/app_dev.php/_profiler/8ad8b2
X-Powered-By:PHP/5.4.14
Request Headers
view source
Accept:application/json, text/plain, */*
Accept-Encoding:gzip, deflate
Accept-Language:fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4
Connection:keep-alive
Content-Length:45
Content-Type:application/json;charset=UTF-8
Cookie:PHPSESSID=ahs3ichmshnqqg5nnu0fkdion6
Host:127.0.0.1
Origin:http://127.0.0.1
Referer:http://127.0.0.1/ek-php/workspace/web/app_dev.php/
User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36
Request Payload
view source
{_username: "admin", _password: "adminpass"}
_password: "adminpass"
_username: "admin"

And that's my dev.log :

[2015-10-21 15:31:14] request.INFO: Matched route "login_check". {"route_parameters":{"_controller":"AppBundle\\Controller\\UserController::loginCheckAction","_route":"login_check"},"request_uri":"http://127.0.0.1/ek-php/workspace/web/app_dev.php/api/login_check"} []
[2015-10-21 15:31:14] security.INFO: Authentication request failed. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\BadCredentialsException(code: 0): Bad credentials. at C:\\Users\\mant\\Documents\\WAMP\\data\\localweb\\ek-php\\workspace\\vendor\\symfony\\symfony\\src\\Symfony\\Component\\Security\\Core\\Authentication\\Provider\\UserAuthenticationProvider.php:73, Symfony\\Component\\Security\\Core\\Exception\\UsernameNotFoundException(code: 0): Username \"NONE_PROVIDED\" does not exist. at C:\\Users\\mant\\Documents\\WAMP\\data\\localweb\\ek-php\\workspace\\vendor\\symfony\\symfony\\src\\Symfony\\Component\\Security\\Core\\User\\InMemoryUserProvider.php:71)"} []

It say : Username \"NONE_PROVIDED\" does not exist.

At that moment, I try with installing FOS Rest Bundle, JSM Serializer and Nelmio CORS Bundle, like in the example application.

If all are activated and configured i got this error :

InvalidArgumentException in ContainerBuilder.php line 868:
The service definition "fos_rest.exception_format_negotiator" does not exist.

I think, i'm near the solution.

Upgrade :

If you need the config.yml :

lexik_jwt_authentication:
    private_key_path: "%jwt_private_key_path%"
    public_key_path:  "%jwt_public_key_path%"
    pass_phrase:      "%jwt_key_pass_phrase%"
    token_ttl:        "%jwt_token_ttl%"

sensio_framework_extra:
    view:    { annotations: false }
    router:  { annotations: true }
    request: { converters: true }

# JMS Serializer
jms_serializer:
    metadata:
        auto_detection: true

# FOS Rest
fos_rest:
    format_listener:
        rules:
            - { path: '^/api', priorities: ['json'], fallback_format: json, prefer_extension: false }
            - { path: '^/', priorities: ['html'], fallback_format: html, prefer_extension: false }
    view:
        view_response_listener: force
        formats:
            json: true
            xml:  false
    routing_loader:
        default_format: json
    serializer:
        serialize_null: true
    param_fetcher_listener: true
    body_listener: true
    access_denied_listener:
        json: true
slashfan commented 8 years ago

Hi, my guess would be that the login_check receives a JSON payload and so cannot find the _username and _password post data. You can etiher send form encoded data from AngularJS instead of a JSON payload, or configure FOSRestBundle to automatically decode it for you.

Have you looked at the LexikJWTAuthenticationBundleSandbox ? Maybe the FOSRestBundle configuration has changed since the version used in the sandbox, but both CURL and AngularJS work in it.

Morgiver commented 8 years ago

Ok, , it's working on the log file, my user is authenticated, but, i don't get the Token in response.

My new function login in AngularJS :

function loginCheck(data) {
            var defer = $q.defer();

            $http({
                url: 'api/login_check',
                method: 'POST',
                data: $httpParamSerializerJQLike(data),
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                }
            }).then(function(success) {
                defer.resolve(success);
            }, function(error){
                defer.reject(error);
            });
            return defer.promise;
        }

The log file

[2015-10-22 10:38:25] request.INFO: Matched route "login_check". {"route_parameters":{"_controller":"AppBundle\\Controller\\UserController::loginCheckAction","_route":"login_check"},"request_uri":"http://127.0.0.1/ek-php/workspace/web/app_dev.php/api/login_check"} []
[2015-10-22 10:38:26] security.INFO: User has been authenticated successfully. {"username":"admin"} []

So now, i can't get the response, maybe he doesn't make it, because i got a "net::ERR_CONNECTION_RESET" in the chrome console.

Reminder : Now, only the JWTAuthenticationBundle is ACTIVE

EDIT : Ha ben merde, j'avais pas vu que tu parlais français ! :-p Ça m'arrange, j'ai du mal à bien m'exprimer en Anglais.

slashfan commented 8 years ago

I'm french yes but it would be better to write in english, your issue might help someone else.

Using xdebug or a simple die() could you check if this method of the success handler is called ?

Morgiver commented 8 years ago

UPGRADE : I try with a new install, with all the changes i was thinking there's was too many thing that i didn't control. The solution of this problem is to verify the access of private key and get an encoded form with AngularJS So it's working fully now.

Thx for your help and patience !

! ALL UNDER IS FOR INFORMATIONS VERIFY YOUR PRIVATE KEY ACCESS !

I just set a die() in begining of the onAuthenticationSuccess(), the response is a simple 200 "OK".

Upgrade :

I think i found where is the point. I put die() to response the $user var, i get a 200 status and "admin" data. But, when i put die() to response the $jwt var, i'm getting a "net::ERR_CONNECTION_RESET"

public function onAuthenticationSuccess(Request $request, TokenInterface $token)
    {
        $user = $token->getUser();
        $jwt  = $this->jwtManager->create($user);
        die($jwt);

        $response = new JsonResponse();
        $event = new AuthenticationSuccessEvent(array('token' => $jwt), $user, $request, $response);

        $this->dispatcher->dispatch(Events::AUTHENTICATION_SUCCESS, $event);

        $response->setData($event->getData());

        return $response;
    }

So i decided to continue the investigation and find the source of connexion reset, that's what i found for the moment : (i update in real time)

Here, the connexion reset happen on $jwtString;

public function create(UserInterface $user)
    {
        $payload = array(
            'exp' => time() + $this->ttl
        );

        $this->addUserIdentityToPayload($user, $payload);

        $jwtCreatedEvent = new JWTCreatedEvent($payload, $user, $this->request);
        $this->dispatcher->dispatch(Events::JWT_CREATED, $jwtCreatedEvent);

        $jwtString = $this->jwtEncoder->encode($jwtCreatedEvent->getData());
        die($jwtString);
        $jwtEncodedEvent = new JWTEncodedEvent($jwtString);
        $this->dispatcher->dispatch(Events::JWT_ENCODED, $jwtEncodedEvent);

        return $jwtString;
    }

Going deeper, and finaly found the source, i think it can't read the private key, or at least, it can't sign the token. I try to die($this->getPrivateKey()); i got a response : Status 200, data "Resource id #214"

public function encode(array $data)
    {
        $jws = new JWS(self::ALGORYTHM);
        $jws->setPayload($data);
        $jws->sign($this->getPrivateKey());
        die();
        return $jws->getTokenString();
    }
slashfan commented 8 years ago

@Morgiver Did you find the source of the problem ?

Morgiver commented 8 years ago

Yes, i think it was a problem with the rights app/var/jwt folder

The RESET happened when it try to read the private key file.

J'avais transféré le dossier de développement sur mon ordi, sans checker s'il y avait eu un changement de droits de dossier, bref, j'me suis un peu perdu et j'ai un peu bourriné en faisant une nouvelle installation.

janjango commented 8 years ago

I have the same problem and symfony-json-request-transformer bundle solve it.

chakgou commented 7 years ago

This is my solution. It works for me with $http Angular.

$http({
                url: Routing.generate('api_login_check'),
                method: 'POST',
                transformRequest: function(obj) {
                    var str = [];
                    for(var p in obj)
                        str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
                    return str.join("&");
                },
                data : {"username":admin.username, "password":admin.password},
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                ignoreAuthModule: true

            })

Vive la response 200 OK :)