Auth0 Home Blog Docs

Auth0-Java: Renewing Tokens

Hi There, I am using auth0-java 1.9.1 from Spring Boot to interact with Auth0’s management API. How to do that is quite nicely described at https://github.com/auth0/auth0-java but I am wondering how we are supposed to handle token renewal. I assumed that the SDK takes care of this but obviously it doesn’t: I receive

com.auth0.exception.APIException: Request failed with status code 401: Expired token received for JSON Web Token validation

when the token has expired. Is it safe to assume that an APIException with 401 is always due to expiration of the token? If not, I wonder if there’s a different way to react to this situation.

At the moment, the only idea I have to to

  1. Put try-catch around every call to the ManagementAPI object and catch APIExceptions
  2. If the status code is 401, I execute a method to get a new access token and set it.
  3. Retry

Has anybody found a more convenient way?

UPDATE:
I almost found a solution, but I still need some help with it. I found out Auth0-Java uses okhttp3 and that library supports interceptors. Looking at the auth0-java code, I found that ManagementAPI creates that okhttp3 client and passes it to UsersEntity and others.

Unfortunately, the “client” field in ManagementAPI cannot be retrieved as all accessors are private, so there seems to be no way to add a custom interceptor to the okhttp3 client used in ManagementAPI. Therefore, I extended ManagementAPI with my own implementation and appended the following interceptor:

 @Override
 public Response intercept(Chain chain) throws IOException {
     Request request = chain.request();

     LOG.info(request.headers());

     Response response = chain.proceed(request);

     if (response.code() == 401) {
         LOG.info(response.headers());

         AuthRequest authRequest = authApi.requestToken(domain + "/api/v2/");
         this.tokenHolder = authRequest.execute();

         mgmtApi.setApiToken(this.tokenHolder.getAccessToken());

         LOG.info("Successfully renewed access token. Retrying request ...");

         Request newRequest = request.newBuilder().removeHeader("Authorization")
                 .addHeader("Authorization", "Bearer: " + this.tokenHolder.getAccessToken()).build();

         LOG.info(request.headers());

         response = chain.proceed(newRequest);
     }

     return response;
 }

What I am trying to do is: When a response returns a 401 error (unauthorized), I create a new authentication request and receive a new access token. Then I set this new token to the ManagementAPI. Next, I want to retry the request that just failed. I remove the existing Authorization header and replace it with a new one that contains the new token. Then I run the request through the chain again.

This is what I see in the logs:

###### REQUEST:
2019-03-13 08:33:38.248  INFO 15088 --- [nio-2222-exec-3] d.e.portal.data.Auth0ConnectionService   : Authorization: Bearer THE_EXPIRED_TOKEN
Content-Type: application/json
Auth0-Client: eyJuYW1lIjoiYXV0aDAtamF2YSIsInZlcnNpb24iOiIxLjAuMCJ9
######
###### UNAUTHORIZED:
Successfully renewed access token. Retrying request ...
###### NEW REQUEST:
2019-03-13 08:33:38.730  INFO 15088 --- [nio-2222-exec-3] d.e.portal.data.Auth0ConnectionService   : Content-Type: application/json
Auth0-Client: eyJuYW1lIjoiYXV0aDAtamF2YSIsInZlcnNpb24iOiIxLjAuMCJ9
Authorization: Bearer: THE_NEW_TOKEN
######

In response to running “newRequest” through the chain, I get this error:

java.lang.RuntimeException: com.auth0.exception.APIException: Request failed with status code 401: Missing authentication

I compared the old and the new token, and found they are definitely different:

$ md5 token-*
MD5 (token-after) = 1a08b18404df2062e8a88efe572c7945
MD5 (token-before) = 49f526255e9a3d2e2d1c4ef8911dc342

I ran the two tokens through jwt.io:

Old token:

{
  "iss": "https://********.eu.auth0.com/",
  "sub": "9h2zKqB38f4j7ZGFqw4kE1H8AA7yGLfy@clients",
  "aud": "https://*****.eu.auth0.com/api/v2/",
  "iat": 1552462283,
  "exp": 1552462343,
  "azp": "9h2zKqB38f4j7ZGFqw4kE1H8AA7yGLfy",
  "scope": "read:client_grants create:client_grants delete:client_grants update:client_grants read:users update:users delete:users create:users read:users_app_metadata update:users_app_metadata delete:users_app_metadata create:users_app_metadata create:user_tickets read:clients update:clients delete:clients create:clients read:client_keys update:client_keys delete:client_keys create:client_keys read:connections update:connections delete:connections create:connections read:resource_servers update:resource_servers delete:resource_servers create:resource_servers read:device_credentials update:device_credentials delete:device_credentials create:device_credentials read:rules update:rules delete:rules create:rules read:rules_configs update:rules_configs delete:rules_configs read:email_provider update:email_provider delete:email_provider create:email_provider blacklist:tokens read:stats read:tenant_settings update:tenant_settings read:logs read:shields create:shields delete:shields update:triggers read:triggers read:grants delete:grants read:guardian_factors update:guardian_factors read:guardian_enrollments delete:guardian_enrollments create:guardian_enrollment_tickets read:user_idp_tokens create:passwords_checking_job delete:passwords_checking_job read:custom_domains delete:custom_domains create:custom_domains read:email_templates create:email_templates update:email_templates read:mfa_policies update:mfa_policies read:roles create:roles delete:roles update:roles",
  "gty": "client-credentials"
}

New token:

{
  "iss": "https://*******.eu.auth0.com/",
  "sub": "9h2zKqB38f4j7ZGFqw4kE1H8AA7yGLfy@clients",
  "aud": "https://*******.eu.auth0.com/api/v2/",
  "iat": 1552462418,
  "exp": 1552462478,
  "azp": "9h2zKqB38f4j7ZGFqw4kE1H8AA7yGLfy",
  "scope": "read:client_grants create:client_grants delete:client_grants update:client_grants read:users update:users delete:users create:users read:users_app_metadata update:users_app_metadata delete:users_app_metadata create:users_app_metadata create:user_tickets read:clients update:clients delete:clients create:clients read:client_keys update:client_keys delete:client_keys create:client_keys read:connections update:connections delete:connections create:connections read:resource_servers update:resource_servers delete:resource_servers create:resource_servers read:device_credentials update:device_credentials delete:device_credentials create:device_credentials read:rules update:rules delete:rules create:rules read:rules_configs update:rules_configs delete:rules_configs read:email_provider update:email_provider delete:email_provider create:email_provider blacklist:tokens read:stats read:tenant_settings update:tenant_settings read:logs read:shields create:shields delete:shields update:triggers read:triggers read:grants delete:grants read:guardian_factors update:guardian_factors read:guardian_enrollments delete:guardian_enrollments create:guardian_enrollment_tickets read:user_idp_tokens create:passwords_checking_job delete:passwords_checking_job read:custom_domains delete:custom_domains create:custom_domains read:email_templates create:email_templates update:email_templates read:mfa_policies update:mfa_policies read:roles create:roles delete:roles update:roles",
  "gty": "client-credentials"
}

The diff shows that only IAT and EXP are differnt, which makes sense:

$ diff token-before-decoded token-after-decoded
5,6c5,6
<   "iat": 1552462283,
<   "exp": 1552462343,
---
>   "iat": 1552462418,
>   "exp": 1552462478,

Everything seems to be working as expected except the “Missing authentication” error I am receiving.

Any ideas?

Just bumped into the same issue. I was able to solve it by storing the token expiration time next to the accessToken itself (auth0-java-1.13.3):

TokenHolder tokenHolder = authAPI.requestToken("https://" + AUTH0_DOMAIN + "/api/v2/")
                                 .execute();
String accessToken = tokenHolder.getAccessToken();
long accessTokenExpiresIn = tokenHolder.getExpiresIn();

With that in place one can trigger a refresh (or get a new) token before the existing one expires before an actual API call.

Thanks a lot for sharing that info with the rest of community!