We have an existing application that uses spring security rest as a security mechanism and we decided to switch to Auth0. I am trying to make grails spring security rest plugin work with Auth0 tokens. Based on the documentation, it seems that the easiest way of achieving it is by implementing custom Token Storage which will validate the token against Auth0
Right now, I simply created my own Token storage class to start understanding how exactly this works.
My security storage implementation is currently exactly the same as The original JwtTokenStorage
(while I am trying to understand the mechanism):
package priz.be
import com.nimbusds.jose.JOSEException
import com.nimbusds.jwt.JWT
import grails.plugin.springsecurity.rest.JwtService
import grails.plugin.springsecurity.rest.token.storage.TokenNotFoundException
import grails.plugin.springsecurity.rest.token.storage.TokenStorageService
import grails.plugin.springsecurity.rest.token.storage.jwt.JwtTokenStorageService
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import java.text.ParseException
@Slf4j
@CompileStatic
class Auth0TokenStorageService extends JwtTokenStorageService {
JwtService jwtService
UserDetailsService userDetailsService
@Override
UserDetails loadUserByToken(String tokenValue) throws TokenNotFoundException {
Date now = new Date()
try {
JWT jwt = jwtService.parse(tokenValue)
if (jwt.JWTClaimsSet.expirationTime?.before(now)) {
throw new TokenNotFoundException("Token ${tokenValue} has expired")
}
boolean isRefreshToken = jwt.JWTClaimsSet.expirationTime == null
if (isRefreshToken) {
UserDetails principal = userDetailsService.loadUserByUsername(jwt.JWTClaimsSet.subject)
if (!principal) {
throw new TokenNotFoundException("Token no longer valid, principal not found")
}
if (!principal.enabled) {
throw new TokenNotFoundException("Token no longer valid, account disabled")
}
if (!principal.accountNonExpired) {
throw new TokenNotFoundException("Token no longer valid, account expired")
}
if (!principal.accountNonLocked) {
throw new TokenNotFoundException("Token no longer valid, account locked")
}
if (!principal.credentialsNonExpired) {
throw new TokenNotFoundException("Token no longer valid, credentials expired")
}
return principal
}
def roles = jwt.JWTClaimsSet.getStringArrayClaim('roles')?.collect { String role -> new SimpleGrantedAuthority(role) }
log.debug "Successfully verified JWT"
log.debug "Trying to deserialize the principal object"
try {
UserDetails details = JwtService.deserialize(jwt.JWTClaimsSet.getStringClaim('principal'))
log.debug "UserDetails deserialized: ${details}"
if (details) {
return details
}
} catch (exception) {
log.debug(exception.message)
}
log.debug "Returning a org.springframework.security.core.userdetails.User instance"
return new User(jwt.JWTClaimsSet.subject, 'N/A', roles)
} catch (ParseException pe) {
throw new TokenNotFoundException("Token ${tokenValue} is not valid")
} catch (JOSEException je) {
throw new TokenNotFoundException("Token ${tokenValue} has an invalid signature")
}
}
@Override
void storeToken(String tokenValue, UserDetails principal) {
log.debug "Nothing to store as this is a stateless implementation"
}
@Override
void removeToken(String tokenValue) throws TokenNotFoundException {
log.debug "Nothing to remove as this is a stateless implementation"
throw new TokenNotFoundException("Token ${tokenValue} cannot be removed as this is a stateless implementation")
}
}
If I send a request to the API with valid Auth0 Token I am getting an invalid token error as a response.
Digging a bit deeper, I found out that while the code is trying to validate the token, it goes into JwtService.parse
function, where it resolves the token as HMAC signed
(not sure why, but let' assume for a moment it is correct) in here:
JWT parse(String tokenValue) {
JWT jwt = JWTParser.parse(tokenValue)
if (jwt instanceof SignedJWT) {
log.debug "Parsed an HMAC signed JWT"
SignedJWT signedJwt = jwt as SignedJWT
if(!signedJwt.verify(new MACVerifier(jwtSecret))) {
throw new JOSEException('Invalid signature')
}
} else if (jwt instanceof EncryptedJWT) {
...
return jwt
}
Then it tries to delegate the verification to MACVerifier
, however, when it's resolving the Algorithm name it fails as RS256 is not supported:
protected static String getJCAAlgorithmName(final JWSAlgorithm alg)
throws JOSEException {
if (alg.equals(JWSAlgorithm.HS256)) {
return "HMACSHA256";
} else if (alg.equals(JWSAlgorithm.HS384)) {
return "HMACSHA384";
} else if (alg.equals(JWSAlgorithm.HS512)) {
return "HMACSHA512";
} else {
throw new JOSEException(AlgorithmSupportMessage.unsupportedJWSAlgorithm(
alg,
SUPPORTED_ALGORITHMS));
}
}
I am pretty sure that it something that I am doing wrong, just can think of anything at this point. What am I missing here?