Issue
Ok, so I have a React component that I only want loaded if JWT
in local storage is valid (meaning token is not expired and it is the correct token). Currently, I am only able to check one of those things, if it is expired or not.
This is the componentDidMount
function in that component:
componentDidMount() {
this._isMounted = true;
// validate token
const token = localStorage.getItem("token");
AuthService.validateToken()
.then((res) => {
console.log("step 1");
if (res == undefined || res == null || !token) {
this.props.history.push("/login");
}
})
.then(() => {
console.log("step 2");
TicketService.getTickets().then((res) => {
if (this._isMounted) {
this.setState({ tickets: res.data });
}
});
});
}
The AuthService.validateToken()
looks like this:
// IS token expired?
validateToken() {
console.log(" about to try validating");
return AxiosInstance.get("authenticate")
.then("did it")
.catch((err) => console.log(err));
}
}
And the AxiosInstance
is just that. I'll share the code to my Axios Instance as well:
const AxiosInstance = axios.create({
baseURL: API_BASE_URL,
headers: { Authorization: `Bearer ${getTokenFromLocalStorage()}` },
});
function getTokenFromLocalStorage() {
const token = localStorage.getItem("token");
console.log("the token is -> " + token);
if (token === null) {
return undefined;
}
return token;
}
Finally, the end point looks like this:
// returns the username
@GetMapping("/authenticate")
public ResponseEntity<?> validateHeaderToken() throws Exception {
String username = null;
try {
System.out.println("Attempting to validate token");
String token = request.getHeader("Authorization");
token = token.split("Bearer ")[1];
username = jwtTokenUtil.extractUsername(token);
// TODO : this should eventually use the .validateToken method once I figure out
// how to pass in UserDetails
if (!jwtTokenUtil.isTokenExpired(token)) {
return ResponseEntity.ok(username);
} else {
throw new Exception("Expired or Invalid Token");
}
} catch (Exception e) {
return ResponseEntity.status(400).build();
}
}
So as you can see, I am only checking if the token is expired in the function above ^^. I want to use my JWT Utility to use the validateToken
that already exists. This is my JWT Utility:
public String extractUsername(String token) {
// we set the username as the subject when we created the token so we can
// extract the username from the claim like this
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
// extract the claim that is attached to the token, if any
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
public Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
// create token using the username as subject
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
// Expiration is 10 hours from creation
// subject is username
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
So, all of the useful functions are there in my JWT Utility, but validateToken()
requires UserDetails
AND token
. Where and how do I get the UserDetails
to pass into the validateToken
function??
EDIT: the user details are already loaded, when I refresh my page, I can see the user details printed for whoever is logged in, but not sure how to access it from other places.
EDIT: MyUserDetailsService
looks like this:
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private EmployeeServices employeeServices;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
System.out.println("S is -> " + s);
Employee employee = employeeServices.getEmployeeByUsername(s);
System.out.println(employee);
// if (employee == null) {
// throw new UsernameNotFoundException("Username was not found. Could not log
// in.");
// }
return new User(employee.getUsername(), employee.getPassword(), new ArrayList<>());
}
}
And when I refresh my page, this gets called each time. I can see the S is ->
printed to my terminal on each page refresh. Is this where I should store User Details in a Cookie?
Solution
It sounds like what you want to do is compare the data in the cookie to the data in UserDetails
.
Presumably you have the UserDetails
stored in a session cookie, or at least an ID for the user. Without knowing exactly how your session cookies are formed, I can't give an exact answer, but here's the gist of it.
Inside your validateHeaderToken
method, where you have access to the incoming request object, you should either:
- Extract the user details from the cookies on the incoming request, or
- Extract the ID from the cookies on the incoming request, and look up the corresponding user details from your database.
then
- Pass the user details along with the token to your
validateToken
method.
MDN article on cookies in headers: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie
Answered By - Matt Morgan