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:
When I use postman to post to http://localhost:8002/api/login_check, I am receiving a legit JWT token:
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
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);
});
}
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?