User authentication is an essential part of Open Event Orga App (Github Repo), which allows an organizer to log in and perform actions on the event he/she organizes. Backend for the application, Open Event Orga Server sends an authentication token on successful login, and all subsequent privileged API requests must include this token. The token is a JWT (Javascript Web Token) which includes certain information about the user, such as identifier and information about from when will the token be valid, when will it expire and a signature to verify if it was tampered.
Parsing the Token
Our job was to parse the token to find two fields:
- Identifier of user
- Expiry time of the token
We stored the token in our shared preference file and loaded it from there for any subsequent requests. But, the token expires after 24 hours and we needed our login model to clear it once it has expired and shown the login activity instead.
To do this, we needed to parse the JWT and compare the timestamp stored in the exp field with the current timestamp and determine if the token is expired. The first step in the process was to parse the token, which is essentially a Base 64 encoded JSON string with sections separated by periods. The sections are as follows:
- Header ( Contains information about algorithm used to encode JWT, etc )
- Payload ( The data in JWT – exp. Iar, nbf, identity, etc )
- Signature ( Verification signature of JWT )
We were interested in payload and for getting the JSON string from the token, we could have used Android’s Base64 class to decode the token, but we wanted to unit test all the util functions and that is why we opted for a custom Base64 class for only decoding our token.
So, first we split the token by the period and decoded each part and stored it in a SparseArrayCompat
public static SparseArrayCompat<String> decode(String token) { SparseArrayCompat<String> decoded = new SparseArrayCompat<>(2); String[] split = token.split("\\."); decoded.append(0, getJson(split[0])); decoded.append(1, getJson(split[1])); return decoded; }
The getJson function is primarily decoding the Base64 string
private static String getJson(String strEncoded) { byte[] decodedBytes = Base64Utils.decode(strEncoded); return new String(decodedBytes); }
The decoded information was stored in this way
0={"alg":"HS256","typ":"JWT"}, 1={"nbf":1495745400,"iat":1495745400,"exp":1495745800,"identity":344}
Extracting Information
Next, we create a function to get the expiry timestamp from the token. We could use GSON or Jackson for the task, but we did not want to map fields into any object. So we simply used JSONObject class which Android provides. It took 5 ms on average to parse the JSON instead of 150 ms by GSON
public static long getExpiry(String token) throws JSONException { SparseArrayCompat<String> decoded = decode(token); // We are using JSONObject instead of GSON as it takes about 5 ms instead of 150 ms taken by GSON return Long.parseLong(new JSONObject(decoded.get(1)).get("exp").toString()); }
Next, we wanted to get the ID of user from token to determine if a new user is logging in or an old one, so that we can clear the database for new user.
public static int getIdentity(String token) throws JSONException { SparseArrayCompat<String> decoded = decode(token); return Integer.parseInt(new JSONObject(decoded.get(1)).get("identity").toString()); }
Validating the token
After this, we needed to create a function that tells if a stored token is expired or not. With all the right functions in place, it was just a matter of comparing current time with the stored timestamp
public static boolean isExpired(String token) { long expiry; try { expiry = getExpiry(token); } catch (JSONException jse) { return true; } return System.currentTimeMillis()/1000 >= expiry; }
Since the token provides timestamp from epoch in terms of seconds, we needed to divide the current time in milliseconds by 1000 and the function returned true if current timestamp was greater than the expiry time of token.
After writing a few unit tests for both functions, we just needed to plug them in our login model at the time of authentication.
At the time of starting of the application, we use this function to check if a user is logged in or not:
public boolean isLoggedIn() { String token = utilModel.getToken(); return token != null && !JWTUtils.isExpired(token); }
So, if there is no token or the token is expired, we do not automatically login the user and show the login screen.
Implementing login
The next task were
- Sequest the server to login
- Store the acquired token
- Delete database if it is a new user
Before implementing the above logic, we needed to implement a function to determine if the person logging in is previous user, or new one. For doing so, we first loaded the saved user from our database, if the query is empty, surely it is a new user logging in. So we return false, and if there is a user in the database, we match its ID with the logged in user’s ID:
public Single<Boolean> isPreviousUser(String token) { return databaseRepository.getAllItems(User.class) .first(EMPTY) .map(user -> !user.equals(EMPTY) && user.getId() == JWTUtils.getIdentity(token)); }
We have added a default user EMPTY in the first operator so that RxJava returns it if there are no users in the database and then we simply map the user to a boolean denoting if they are same or different using the EMPTY user and getIdentity method from JWTUtils
Finally, we use all this information to implement our self contained login request:
eventService .login(new Login(username, password)) .flatMapSingle(loginResponse -> { String token = loginResponse.getAccessToken(); utilModel.saveToken(token); return isPreviousUser(token); }) .flatMapCompletable(isPrevious -> { if (!isPrevious) return utilModel.deleteDatabase(); return Completable.complete(); });
Let’s see what is happening here. A request using username and password is made to the server which returns a login response containing a JWT, which we store for future use. Next, we flatMapSingle to the Single returned by the isPreviousUser method. And we finally clear the database if it is not a previous user.
Creating these self contained models help reduce complexity in presenter or view layer and all data is handled in one layer making presenter layer model agnostic.
To learn more about JWT and some of the Rx operators I mentioned here, please visit these links:
- Interactive JWT Playground and information about JWT and libraries: https://jwt.io/
- Explanation of JWT sections and working https://medium.com/vandium-software/5-easy-steps-to-understanding-json-web-tokens-jwt-1164c0adfcec
- JWT Wikipedia Article https://en.wikipedia.org/wiki/JSON_Web_Token
- Rx Flatmap operator http://reactivex.io/documentation/operators/flatmap.html
- Rx Map operator http://reactivex.io/documentation/operators/map.html
- JSONObject Android documentation https://developer.android.com/reference/org/json/JSONObject.html