0

I am using React 16.13.1 for my login and signup forms with Material-UI, Symfony 5.0.7, Web server is using PHP FPM 7.3.8

When I submit a form, I am not see JWT in the headers:

screen shot of headers

When I use postman to post to http://localhost:8002/api/login_check, I am receiving a legit JWT token:

screen shot of postman

Within the api-platform, I click Authorize, insert my token, followed by doing a search for id=3, then it returns with a status 200, along with the correct info

screen shot of api-platform results

My config/packages/security.yaml:

security:
    encoders:
        App\Entity\User:
            algorithm: auto

    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email

    role_hierarchy:
        ROLE_ADMIN: [ROLE_ALLOWED_TO_SWITCH]

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

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

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

        main:
            stateless: true
            anonymous: lazy
            provider: app_user_provider
            json_login:
                check_path: login
                username_path: email
                password_path: password

            guard:
                authenticators:
                    - App\Security\TokenAuthenticator
                entry_point: App\Security\TokenAuthenticator

            logout:
                path: logout

            remember_me:
                secret:   '%kernel.secret%'
                lifetime: 2592000 # 30 days in seconds
                path: /
                always_remember_me: true

            switch_user: true


            # activate different ways to authenticate
            # https://symfony.com/doc/current/security.html#firewalls-authentication

            # https://symfony.com/doc/current/security/impersonating_user.html
            # switch_user: true

    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
    # - { path: ^/admin, roles: ROLE_ADMIN }
    # - { path: ^/dashboard, roles: ROLE_USER }

My config/packages/api_platform.yaml:

api_platform:
    mapping:
        paths: ['%kernel.project_dir%/src/Entity']
    patch_formats:
        json: ['application/merge-patch+json']
    swagger:
        versions: [3]
        api_keys:
            apiKey:
                name: Authorization
                type: header

Within the React form, my fetch is posting to /login, which uses an Authenticator that I created:

    async handleClick() {
        const requestOptions = {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
                'credentials': "same-origin"
            },
            body: JSON.stringify({
                'email' : this.state.email,
                'password' : this.state.password,
                'username' : this.state.username,
                '_remember_me' : this.state._remember_me
            })
        };
        fetch('/login', requestOptions)
            .then((response) => {
                if (response.error) {
                    let errMsg = response.error;
                    this.setState({ errorMsg:  errMsg});
                }
               this.setNotifications(response);
            }, (error) => {
                let errMsg = error.message;
                this.setState({ errorMsg:  errMsg});
                this.setNotifications(error);
            });
    }

In my authenticator, do I need to somehow add the JWT to my support function?

public function supports(Request $request)
{
    if ($request->getMethod() == 'POST') {
        return $request->isMethod('POST') && 'login' === $request->attributes->get('_route');
    } else {
        return $request->isMethod('GET') && 'login' === $request->attributes->get('_route');
    }
}

Do I need to add the JWT to my login function within my controller?

public function login(AuthenticationUtils $authenticationUtils)
{
    // get the login error if there is one
    $error = $authenticationUtils->getLastAuthenticationError();

    // last username entered by the user
    $lastUsername = $authenticationUtils->getLastUsername();

    if (!$this->isGranted('IS_AUTHENTICATED_FULLY')) {
        return $this->json([
            'error' => 'Invalid login request: check that the Content-Type header is "application/json".'
        ], 400);
    }

    return $this->json([
        'last_username' => $this->getUser()->getUsername(),
        'error'         => $error,
        'user' => $this->getUser() ? $this->getUser()->getId() : null,
        'username' => $this->getUser()->getUsername()
    ]);
}

How do I use JWT with my current authentication process to allow the headers to show JWT, the way I used to do with Angular 6, where I would create an HttpInterceptor and pass the JWT to the headers:

intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const idToken = localStorage.getItem("jwt");

if (idToken) {
  const cloned = req.clone({
    headers: req.headers.set(
      "Authorization",
      "Bearer " + this.loginSrvc.getToken()
    )
  });

  const prodCloned = req.clone({
    headers: req.headers.set(
      "Authorization",
      "Bearer " + this.prodSrvc.getToken()
    )
  });

return next.handle(prodCloned);
}

getDecodedAccessToken(token: string): any {
try {
  return jwt_decode(token);
} catch (error) {
  console.log("error: ", error);
  return false;
}
}

Back then I felt as though all my header information was somewhat protected and that is what I am trying to reproduce with React to Symfony.

OK, this is probably not the way to do it, but I feel like I am on the right track.

I first post to my /api/login_check with the user's credentials, then I pass the response to be added to the header of my login fetch

getJWT is called when the user clicks the Login button:

    async getJWT(){
        const requestOptionsJWT = {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
                'credentials': "same-origin"
            },
            body: JSON.stringify({
                'username' : this.state.email,
                'password' : this.state.password
            })
        };
        await fetch('/api/login_check', requestOptionsJWT)
            .then((responseJWT) => {
                return responseJWT.json();
            })
            .then((parsedData) => {
                let jwtParam = parsedData.token;
                this.setState({'jwt': jwtParam});
                this.handleClick();
            })
    }

    async handleClick() {
        console.log('param: ' + JSON.stringify(this.state.jwt));
        const requestOptions = {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
                'credentials': "same-origin",
                "Authorization" : "Bearer " + this.state.jwt
            },
            body: JSON.stringify({
                'email' : this.state.email,
                'password' : this.state.password,
                'username' : this.state.username,
                '_remember_me' : this.state._remember_me
            })
        };
        fetch('/login', requestOptions)
            .then((response) => {
                if (response.error) {
                    let errMsg = response.error;
                    this.setState({ errorMsg:  errMsg});
                }
               this.setNotifications(response);
            }, (error) => {
                let errMsg = error.message;
                this.setState({ errorMsg:  errMsg});
                this.setNotifications(error);
            });
    }

screen shot showing JWT in header

The result is that I see JWT in the headers of the fetch to login, but I still do not see the same for the headers when I receive the JWT in the first place. Is there a JWT encoder?

yivi
  • 42,438
  • 18
  • 116
  • 138
kronus
  • 902
  • 2
  • 16
  • 34
  • What do you mean "in the headers"? The JWT token is not returned in the response header, but in the response **body**. What you see in postman is the body, and what you show in the other screenshot are the headers. Two very different things. – yivi May 14 '20 at 15:04
  • Thank you for responding @yivi I want to see under the headers tab of the developer tools something that says Authorization Bear... As you can see with the last screen shot. Currently, I am calling the /api/login_check endpoint and entering the token into the headers of the second fetch, before posting, but there has to be a better way than to have two fetch calls – kronus May 14 '20 at 17:37
  • Again, the token will not be sent on the response headers, but on the response body. Do not check on "headers", but on "response". – yivi May 14 '20 at 17:39
  • @yivi response is completely blank. BTW, it works fine locally but on the VPS I am receiving a 401 Unauthorized – kronus May 14 '20 at 20:12

0 Answers0