Auth0 Jwt strange behavior - jwt

I want to use jwt as authentication mechanism so I pick auth0-jwt java libs. To implement authentication services I did a test about token validation. The test is simple, use this token:
{ "alg":"HS256", "typ": "JWT" }
{ "sub": "dummySubject", "exp": "1498054620653" }
with supersecretpassphrase to verify signature. Given that token I expect back this (taken by jwt.io) one:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkdW1teVN1YmplY3QiLCJleHAiOiIxNDk4MDU0NjIwNjUzIn0.VTL9gIQ-POIoI8W0OI9y_Q7rDGFbniUGNcfd_EliK60
but test fails. The returned token is:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkdW1teVN1YmplY3QiLCJleHAiOjE0OTgwNTQ2MjB9.2dOy3HmWH7vMWiJoQvMOUsx2nR6l6phZ1nwIz-TSkNk
jwt.io says that that token has this shape:
{ "typ": "JWT", "alg": "HS256" }
{ "sub": "dummySubject", "exp": "1498054620" }
It looks like exp loose its milliseconds. This is how I fill my token:
private String computeToken(JwtToken token) throws Exception {
Algorithm algorithmHS = Algorithm.HMAC256(passphrase);
Date expirationTime = new Date(token.getExpirationTime().toInstant().toEpochMilli());
Builder jwtToken = JWT.create().withSubject(token.getSubject()).withExpiresAt(expirationTime);
Iterator<Entry<String, Object>> candidateClaimsIterator = token.getPayload().entrySet().iterator();
...
return jwtToken.sign(algorithmHS);
}
The JwtToken token parameter is a simple bean I use to ship and validate data I want to put in token,
public class JwtToken implements Serializable {
private static final long serialVersionUID = 1L;
#NotNull(message = "Subject can not be null")
#Size(min = 10)
private String subject;
#NotNull(message = "Expiration time can not be null")
private ZonedDateTime expirationTime;
private Map<String, Object> payload;
...
(getters and setters)
}
It seems that java.util.Date loose it's milliseconds while com.auth0.jwt.JWTCreator convert exp in a JSON string. Am I doing something wrong? Is this the expected behavior?

Related

How do we specify expiration date of JWT?

This the time for which we want the generated JWT to be valid for.
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + jwtExpirationInMs)).signWith(SignatureAlgorithm.HS512, secret).compact();
}

Keycloak - Customize "sub" format in JWT token

I'm trying to find a way to change the "sub" format in JWT Token provided by Keycloak, I know it came from Keycloak User Id but i'm not sure we can't change it.
For example for now I have something like this :
"sub": "f:39989175-b393-4fad-8f84-628b9712f93b:testldap",
I would like it smaller 😅.
I'm not sure that modifying 'sub' is a good idea, but if you sure, you can use something like that:
/**
* Class for signing JWT (when you get tokens in base64 actually they are
* signed by issuer server see https://jwt.io)
*/
public static class JwtSigner {
private final KeyPair keyPair;
private final String kid;
public JwtSigner(String privateKeyPem) {
PrivateKey privateKey = PemUtils.decodePrivateKey(privateKeyPem);
PublicKey publicKey = KeyUtils.extractPublicKey(privateKey);
keyPair = new KeyPair(publicKey, privateKey);
kid = KeyUtils.createKeyId(keyPair.getPublic());
}
public String encodeToken(AccessToken accessToken) {
return new JWSBuilder()
.type("JWT")
.kid(kid)
.jsonContent(accessToken)
.sign(Algorithm.RS256, keyPair.getPrivate());
}
}
/**
* This class allows you to update several token fields and re-encode token
*/
public static class JwtTransformer<T extends AccessToken> {
private T token;
public JwtTransformer(String tokenString, Class<T> tokenType) throws JWSInputException {
try {
token = JsonSerialization.readValue(new JWSInput(tokenString).getContent(), tokenType);
} catch (IOException e) {
throw new JWSInputException(e);
}
}
public static <T extends AccessToken> T decode(String tokenString, Class<T> tokenType) throws JWSInputException {
return new JwtTransformer<>(tokenString, tokenType).decode();
}
public static JwtTransformer<AccessToken> forAccessToken(String tokenString) throws JWSInputException {
return new JwtTransformer<>(tokenString, AccessToken.class);
}
public static JwtTransformer<RefreshToken> forRefreshToken(String tokenString) throws JWSInputException {
return new JwtTransformer<>(tokenString, RefreshToken.class);
}
public T decode() {
return token;
}
public JwtTransformer transform(Consumer<T> consumer) {
consumer.accept(token);
return this;
}
public String encode(JwtSigner jwtSigner) {
return jwtSigner.encodeToken(token);
}
}
I used this classes for tests, but you can adopt them for your needs. Take a note that private key that required for JwtSigner initializaton is stored in keycloak DB, and can not be easily extracted via Admin Console UI. Check out result of
select VALUE
from KEYCLOAK.COMPONENT
inner join KEYCLOAK.COMPONENT_CONFIG
on KEYCLOAK.COMPONENT.ID = KEYCLOAK.COMPONENT_CONFIG.COMPONENT_ID
where PARENT_ID = '%YOUR_REALM_NAME%'
and PROVIDER_ID = 'rsa-generated'
and COMPONENT_CONFIG.NAME = 'privateKey';
So finally you can do something like
String new AccessToken = JwtTransformer.forAccessToken(accessTokenString)
.transform(token -> {
token.subject(subModificationFunction(token.getSubject()))
})
.encode();

Security Attacks possible on TokenBased Authentication?

I have designed a web application which uses very simple implementation of JWT token's to provide Authentication/Authorization.
My Implementation :
There are two types of urls's public and secure.
Public urls are to generate token with username/password.
I have added filter on secure url to check for the Authorization Header and JWT Token.
#Bean
public FilterRegistrationBean jwtFilter()
{
final FilterRegistrationBean registrationBean = new
FilterRegistrationBean();
registrationBean.setFilter(new JwtFilter());
registrationBean.addUrlPatterns("/secure/*");
return registrationBean;
}
Filter will validate the token. I haven't added expiration date yet.
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
final String authHeader = request.getHeader("authorization");
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
chain.doFilter(req, res);
} else {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new ServletException("Missing or invalid Authorization header");
}
final String token = authHeader.substring(7);
try {
final Claims claims = Jwts.parser().setSigningKey(secretKey.toString).parseClaimsJws(token).getBody();
request.setAttribute("claims", claims);
} catch (final SignatureException e) {
throw new ServletException("Invalid token");
}
chain.doFilter(req, res);
}
This is providing authentication and also its is immune to CSRF.No one can create valid token without secret Key.
Are there other attacks possible on token base authentication service which i have missed?

Issue JWT tokens from Spring OAuth2 Authorization Server when authenticating with Google

I want to create an authorization server using Spring Oauth which is able to issue it's own JWT tokens. The authorization server must delegate the authentication to Google. I have been following this tutorial which does almost everything I want: https://spring.io/guides/tutorials/spring-boot-oauth2/
I was able to add Google as an authentication provider, but I'm struggling with the JWT part.
Here's my authorization server configuration:
#SpringBootApplication
#EnableOAuth2Client
#EnableAuthorizationServer
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class MsAuthorizationGmailApplication extends WebSecurityConfigurerAdapter {
#Autowired
OAuth2ClientContext oauth2ClientContext;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().antMatchers("/", "/login**", "/webjars/**").permitAll().anyRequest()
.authenticated().and().exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login/gmail")).and().logout()
.logoutSuccessUrl("/").permitAll().and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
}
#Bean
#ConfigurationProperties("gmail")
public ClientResources gmail() {
return new ClientResources();
}
private Filter ssoFilter() {
CompositeFilter filter = new CompositeFilter();
List<Filter> filters = new ArrayList<>();
filters.add(ssoFilter(gmail(), "/login/gmail"));
filter.setFilters(filters);
return filter;
}
private Filter ssoFilter(ClientResources client, String path) {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
path);
OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
filter.setRestTemplate(template);
filter.setTokenServices(JwtConfig.tokenServices());
return filter;
}
public static void main(String[] args) {
SpringApplication.run(MsAuthorizationGmailApplication.class, args);
}
}
In the JWT config I'm not trying to do anything fancy, just trying to make it pass for now:
public final class JwtConfig {
private static final String KEY = "123";
private JwtConfig() {
}
private static JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(KEY);
return converter;
}
private static TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
public static DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
}
I get the following exception:
org.springframework.security.authentication.BadCredentialsException: Could not obtain user details from token
at org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:122) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:112) [spring-web-4.3.6.RELEASE.jar:4.3.6.RELEASE]
at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:73) [spring-web-4.3.6.RELEASE.jar:4.3.6.RELEASE]
....
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.11.jar:8.5.11]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_121]
Caused by: org.springframework.security.oauth2.common.exceptions.InvalidTokenException: Cannot convert access token to JSON
at org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter.decode(JwtAccessTokenConverter.java:287) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.token.store.JwtTokenStore.convertAccessToken(JwtTokenStore.java:88) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.token.store.JwtTokenStore.readAccessToken(JwtTokenStore.java:80) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.token.DefaultTokenServices.loadAuthentication(DefaultTokenServices.java:229) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
at org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:112) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
... 62 common frames omitted
Caused by: java.lang.IllegalArgumentException: JWT must have 3 tokens
at org.springframework.security.jwt.JwtHelper.decode(JwtHelper.java:49) ~[spring-security-jwt-1.0.0.RELEASE.jar:na]
at org.springframework.security.jwt.JwtHelper.decodeAndVerify(JwtHelper.java:74) ~[spring-security-jwt-1.0.0.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter.decode(JwtAccessTokenConverter.java:277) ~[spring-security-oauth2-2.0.12.RELEASE.jar:na]
... 66 common frames omitted
How I understand this: It looks like that when Google issues an access token, the authorization server (being a client of Google OAuth) tries to decode the access token as a JWT, and throws an exception because Google's token is not a valid JWT (it's just an access token).
I would like to create a JWT containing the access token (which will be used to access Google APIs) and some additional information about the user. I would also like to be able to refresh the JWT token when the access token expires. Is there any way to achieve this?
Am not sure about GMail but for your own Authorization server, you can add one token enhancer JwtAccessTokenConverter which will convert you token into JWT.
For sample, pls refer oauth2-spring-boot-mongo-jwt-sample
Generally, normal token payload is of below type
{
"access_token": "bc9c021f-b5ae-43af-9746-737b533f9bc5",
"token_type": "bearer",
"refresh_token": "fee7a2a1-eff9-4757-8dd3-5392ee225bea",
"expires_in": 43199,
"scope": "read-foo" }
whereas, JWT looks something like this
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZm9vIl0sInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyJyZWFkLWZvbyJdLCJleHAiOjE1MTQ3ODMwNTIsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiJlMjM4MDg1YS0xZjFjLTQ5ZWQtODNiMC1iN2Q1MjI5OWUwZjYiLCJjbGllbnRfaWQiOiJ3ZWItY2xpZW50In0.-OSw1Vr4o1dnAQL3n7QFGG6UOXr4itc0Kp8dugyT4zU",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZm9vIl0sInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyJyZWFkLWZvbyJdLCJhdGkiOiJlMjM4MDg1YS0xZjFjLTQ5ZWQtODNiMC1iN2Q1MjI5OWUwZjYiLCJleHAiOjE1MTczMzE4NTIsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiIzYTA2OTZmMy1mYzg1LTQ2YTEtYjVlMC01NmQ2OGVmYTJhMmUiLCJjbGllbnRfaWQiOiJ3ZWItY2xpZW50In0.jSBriPfM-rSgHHLyifIuBHwrwCkyb5I2u2AKa8kQUUU",
"expires_in": 43199,
"scope": "read-foo",
"jti": "e238085a-1f1c-49ed-83b0-b7d52299e0f6"
}

WSO2IS JWT access token

I am trying get a JWT access token from WSO2 IS. I followed instructions from msf4j Oauth2 Security Sample, and managed to get a JWT acces token by resource owner password grant type.
but I have problem authenticating the token externally.
it seems that the token had not been signed by the default "wso2carbon.jks".
also, my claim configurations in the "service providers" was not reflected in jwt content
so my questions: how to config the JWT signing certificate in WSO2IS?
and also:
How to manipulate the claims in the JWT?
I do not want to turn to the "introspect" endpoint out of performance concern, and my strategy is to just trust the IS, only to make sure(locally) of the authenticity of the JWT token
please advise
thanks
You can follow [1] to get JWT Access Tokens(Self contained access tokens) using WSO2 Identity Server
[1] https://medium.com/#hasinthaindrajee/self-contained-access-tokens-with-wso2-identity-server-82111631d5b6
well, it seems to be my own fault.
I had been using the jose4j JWT package, and kept getting verification failed message.
after further checking into the msf4j implementation, I switched over to nimbus-jose-jwt JWT package, and got it done,
below are my implementation.
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
public class JwtParser {
private static final String KEYSTORE = System.getProperty("javax.net.ssl.trustStore");
private static final String KEYSTORE_PASSWORD = System.getProperty("javax.net.ssl.trustStorePassword");
private static Map<String, JWSVerifier> verifiers = getVerifiers();
public static JWTClaimsSet verify(String jwt) throws Exception {
SignedJWT signedJWT = SignedJWT.parse(jwt);
if (!new Date().before(signedJWT.getJWTClaimsSet().getExpirationTime())) {
new Exception("token has expired");
}
boolean notYet = true;
for(Iterator<JWSVerifier> it = verifiers.values().iterator(); notYet && it.hasNext();){
JWSVerifier verifier = it.next();
notYet = !signedJWT.verify(verifier);
}
if(notYet){
throw new Exception("token verification failed");
}
JWTClaimsSet claims = signedJWT.getJWTClaimsSet();
if (claims == null) {
// Do something with claims
throw new Exception("non valid payload in token, failed");
}
return claims;
}
private static Map<String, JWSVerifier> getVerifiers(){
Map<String, JWSVerifier> verifiers = new HashMap<>();
try (InputStream inputStream = new FileInputStream(KEYSTORE)) {
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(inputStream, KEYSTORE_PASSWORD.toCharArray());
Enumeration<String> aliases = keystore.aliases();
while(aliases.hasMoreElements()){
String alias = aliases.nextElement();
if(!keystore.isCertificateEntry(alias)){
continue;
}
Certificate cert = keystore.getCertificate(alias);
if(cert == null){
continue;
}
PublicKey key = cert.getPublicKey();
verifiers.put(alias, new RSASSAVerifier((RSAPublicKey)key));
}
}catch(KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e){
//TODO: report the exception
}
return verifiers;
}
}