This post will primarily be sharing the code I created to work with JWT in Android communicating with a PHP server. This project is done is a test environment but is completely functional.
I don’t plan on posting the UI logic, this is just the backend logic and implementation.
The PHP server holds the user credentials in a MySQL database.
The Android app just holds the access and refresh JWTs received from the PHP server on register or login in.
I set a short expiry on the access token and a long expiry on the refresh token. These can be changed to your liking.
The code could be shorten to make it a bit cleaner. I also have implemented asymmetric encryption for the user data being sent to the server.
Most of what I show here will have some explaining but is fairly straightforward especially if you have viewed my last few posts. These posts were parts of this project or built separately to be implemented in this project.
Code
I will start with the Android code first. The names of each class or object is what I named them in the package. They are sorted in various folders for organization.
ErrorCodes – codes sent from PHP server in JSON, Android receives and processes the UI state based on these. I used negative numbers for ease of use and tracking. User ID’s will always be positive.
data object ErrorCodes {
val NO_ERROR : Int = -10
val EMAIL_EXISTS : Int = -1
val PROBLEM_ADDING_CREDENTIALS : Int = -2
val ERROR_VALIDATING_CREDENTIALS : Int = -3
val CONNECTION_ISSUE : Int = -4
val FORM_DATA_ISSUE : Int = -5
val NO_ACCESS : Int = -6
val TOKEN_ERROR : Int = -9
}
JWT – data type to hold JWT in Retrofit response. The id is used for error handling
data class JWT(
@SerializedName("id")
val id : Int,
@SerializedName("acc_token")
val acc_token : String,
@SerializedName("refresh_token")
val refresh_token : String,
)
TokenSingleton – hold the JWT for user in datastore
class TokenSingleton {
companion object {
lateinit var dataStore: DataStore<Preferences>
@Volatile
private var INSTANCE: TokenSingleton? = null
fun getInstance(context : Context): TokenSingleton? {
if (INSTANCE == null) {
synchronized(this) {
if (INSTANCE == null) {
// create the singleton instance
INSTANCE = TokenSingleton()
dataStore = PreferenceDataStoreFactory.create(
corruptionHandler = ReplaceFileCorruptionHandler(
produceNewData = { emptyPreferences() },
),
produceFile = {
context
.preferencesDataStoreFile("test_jwt_datastore")
}
)
}
}
}
return INSTANCE
}
}
suspend fun getAllInfo() : JWT {
val prefs = dataStore.data.first()
return JWT(-1, prefs[PrefKeys.ACC_TOKEN] ?: PrefKeys.EMPTY, prefs[PrefKeys.REFRESH_TOKEN] ?: PrefKeys.EMPTY)
}
suspend fun getAccToken() : String {
val prefs = dataStore.data.first()
return prefs[PrefKeys.ACC_TOKEN] ?: PrefKeys.EMPTY
}
suspend fun getRefreshToken() : String {
val prefs = dataStore.data.first()
return prefs[PrefKeys.REFRESH_TOKEN] ?: PrefKeys.EMPTY
}
suspend fun updateAccToken(acc_token : String){
dataStore.edit {
it[PrefKeys.ACC_TOKEN] = acc_token
}
}
suspend fun updateRefreshToken(refresh_token : String){
dataStore.edit {
it[PrefKeys.REFRESH_TOKEN] = refresh_token
}
}
suspend fun clearData(){
dataStore.edit { it.clear() }
}
}
private object PrefKeys {
val ACC_TOKEN = stringPreferencesKey("acc_token")
val REFRESH_TOKEN = stringPreferencesKey("refresh_token")
val EMPTY = "HEADER.PAYLOAD.SIGNATURE"
}
AccessAPI – service used in Retrofit for the url and parameters to send as well as what daya types are being received. I had an issue with my PHP and communication here. The fix was to ensure all returned data types are of a list type. This is due to an issue in capturing the error codes from the Authenticator back to the original request for UI processing.
interface AccessApi {
@POST("users_list.php")
fun getUsers() : Call<List<UsersType>>
@POST("register.php")
fun registerUser(@Body userdata : UserData) : Call<JWT>
@POST("login.php")
fun loginUser(@Body userdata : UserData) : Call<JWT>
//used for Authenticator
@POST("authtest.php")
suspend fun jwtauth(): Response<List<JWT>>
UserData – data type to create object to create request to send to server. Used for login and register only. After JWT received, this is no longer used for authentication, just JWT.
data class UserData(
@SerializedName("username")
val username : String,
@SerializedName("email")
val email : String,
@SerializedName("password")
val password : String
)
JWTAuthenticator – this is what the JWTs use
class JWTAuthenticator(val applicationContext : Context) : Authenticator {
private val TAG = JWTAuthenticator::class.java.name
override fun authenticate(route: Route?, response: Response): Request {
val token = runBlocking {
Log.d(TAG, "Access: ${TokenSingleton.getInstance(applicationContext)!!.getAccToken()}")
Log.d(TAG, "Refresh: ${TokenSingleton.getInstance(applicationContext)!!.getRefreshToken()}")
TokenSingleton.getInstance(applicationContext)!!.getRefreshToken()
}
return runBlocking {
if(token.length>1){
val queryNewToken = RetrofitHelperAuth.getInstance(applicationContext).create(AccessApi::class.java)
val queryResponse = queryNewToken.jwtauth()
if(queryResponse.isSuccessful && queryResponse != null){
val result = queryResponse.body()
if(result != null && result.size > 0) {
result[0].acc_token.let { TokenSingleton.getInstance(applicationContext)!!.updateAccToken(it) }
result[0].refresh_token.let {
TokenSingleton.getInstance(applicationContext)!!.updateRefreshToken(
it
)
}
}
Log.d(TAG, "QueryResponse: ${queryResponse.body().toString()}")
Log.d(TAG, "Access: ${TokenSingleton.getInstance(applicationContext)!!.getAccToken()}")
Log.d(TAG, "Refresh: ${TokenSingleton.getInstance(applicationContext)!!.getRefreshToken()}")
}
}
response.request.newBuilder().header("Authorization", "Bearer ${TokenSingleton.getInstance(applicationContext)!!.getAccToken()}")
.build()
}
}
}
JWTAuthInterceptor – a separate interceptor used just for the JWTAuthenticator. This targets the refresh token
class JWTAuthInterceptor(val applicationContext : Context) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token = runBlocking { TokenSingleton.getInstance(applicationContext)!!.getRefreshToken() }
val request = chain.request()
val newRequest = request
.newBuilder()
.header("Authorization", "Bearer $token")
.build()
return chain.proceed(newRequest)
}
}
JWTInterceptor – this interceptor targets the use of the access token
class JWTInterceptor(val applicationContext : Context) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token = runBlocking { TokenSingleton.getInstance(applicationContext)!!.getAccToken() }
val request = chain.request()
val newRequest = request
.newBuilder()
.header("Authorization", "Bearer $token")
.build()
return chain.proceed(newRequest)
}
}
EncryptionInterceptor – used to encrypt the data payload sent to server. The public key is saved in the R.raw resource named pk
class EncryptionInterceptor(val applicationContext : Context) : Interceptor {
private val TAG : String = EncryptionInterceptor::class.java.simpleName
override fun intercept(chain: Interceptor.Chain): Response {
var request: Request = chain.request()
var requestBody = request.body
if (requestBody != null) {
val buffer = Buffer()
val mediaType: MediaType? = "application/x-www-form-urlencoded;charset=UTF-8".toMediaTypeOrNull() //text/plain; charset=utf-8
requestBody.writeTo(buffer)
val contentType = requestBody.contentType()
val charset: Charset = contentType?.charset(UTF_8) ?: UTF_8
val strToEncrypt = buffer.readString(charset)
Log.d(TAG, strToEncrypt)
val secureMsg : String?
if (strToEncrypt.isNotEmpty()) {
secureMsg = processEncryptData(strToEncrypt)
} else {
Log.d(TAG, "{\"id\":500}")
secureMsg = processEncryptData("{\"id\":500}")
}
if (secureMsg != null) {
Log.d(TAG, secureMsg)
val formattedStr = SecureDataMessage(secureMsg)
val jsonObject = Gson().toJson(formattedStr)
Log.d(TAG, jsonObject)
val newRequestBody = RequestBody.create(mediaType, jsonObject)
request = request.newBuilder().url(request.url).headers(request.headers).method(request.method, newRequestBody).build()
}
}
Log.d(TAG, request.method)
//request = request.newBuilder().build()
return chain.proceed(request)
}
private fun processEncryptData(str : String) : String? {
val inputStream: InputStream = applicationContext.resources.openRawResource(R.raw.pk)
val bytes: ByteArray = inputStream.readBytes()
val pk = String(bytes)
Log.d("Public Key", pk)
val encryptedData = SecureMessage.encryptData(str, pk)
if (encryptedData != null) {
Log.d("Encrypted Data", encryptedData)
return encryptedData
}
return null
}
}
SecureDataMessage – data type for encryption
data class SecureDataMessage (
@SerializedName("message")
val message : String
)
SecureMessage – object used to do encryption and decryption
object SecureMessage {
fun getTheKey(pk : String) : String {
val key = pk.replace("\\r".toRegex(), "")
.replace("\\n".toRegex(), "")
.replace(System.lineSeparator().toRegex(), "")
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.trim()
Log.d("Public Key", key)
return key
}
fun encryptData(txt: String, publicKey: String): String? {
val pk = getTheKey(publicKey)
var encoded = ""
var encodedStr = StringBuilder()
var encrypted: ByteArray? = null
try {
val publicBytes: ByteArray = Base64.decode(pk, Base64.DEFAULT)
val keySpec = X509EncodedKeySpec(publicBytes)
val keyFactory: KeyFactory = KeyFactory.getInstance("RSA")
val pubKey: PublicKey = keyFactory.generatePublic(keySpec)
val cipher: Cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING")
cipher.init(Cipher.ENCRYPT_MODE, pubKey)
val encryptTxt = txt.encodeToByteArray()
Log.d("Encrypt Text", "Size of text to encrypt: " + encryptTxt.size.toString())
val blockSize = 53
val convertArray = arrayListOf<ByteArray>()
val encryptedArray = arrayListOf<ByteArray>()
var i = 0
var index = 0
var dstSize = blockSize
if(encryptTxt.size < blockSize){
encrypted = cipher.doFinal(encryptTxt)
encoded = Base64.encode(encrypted, Base64.DEFAULT).decodeToString()
return encoded
}
while (i < encryptTxt.size-1){
Log.d("Encrypt Text", "$i : $dstSize")
val dst = ByteArray(dstSize)
while(index < dstSize){
dst[index] = encryptTxt[i]
index++
i++
}
index = 0
convertArray.add(dst)
encrypted = cipher.doFinal(dst)
encryptedArray.add(Base64.encode(encrypted, Base64.DEFAULT))
Log.d("Encrypt Text", "Sample: " + encryptedArray.get(encryptedArray.size-1).decodeToString())
if(encryptTxt.size - 1 - i < blockSize){
dstSize = encryptTxt.size - i
}
}
encodedStr.apply {
encryptedArray.forEach {
if(this.isNotEmpty()){
this.append(":")
}
this.append(it.decodeToString().trim())
}
}
return encodedStr.toString()
} catch (e: Exception) {
e.printStackTrace()
}
return encoded
}
}
RetrofitHelper – inside this class I have two different instances. This could be cleaned up a bit to use parameters so only one is needed. The top one is for regular requests. The bottom one is for the Authenticator. The main difference is which interceptor to use for what action.
object RetrofitHelper {
val baseUrl = "<server_address>/"
fun getInstance(applicationContext : Context): Retrofit {
val httpLoggingInterceptor: HttpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
val jwtInterceptor: JWTInterceptor = JWTInterceptor(applicationContext)
val jwtAuthenticator = JWTAuthenticator(applicationContext)
val encryptionInterceptor: EncryptionInterceptor = EncryptionInterceptor(applicationContext)
val okHttpClient = OkHttpClient.Builder()
.authenticator(jwtAuthenticator)
.addInterceptor(jwtInterceptor)
.addInterceptor(encryptionInterceptor)
.addInterceptor(httpLoggingInterceptor)
.build()
val gson = GsonBuilder()
.setLenient().create()
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.build()
}
}
object RetrofitHelperAuth {
val baseUrl = "<server_address>/"
fun getInstance(applicationContext : Context): Retrofit {
val httpLoggingInterceptor: HttpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
val jwtAuthInterceptor: JWTAuthInterceptor = JWTAuthInterceptor(applicationContext)
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(jwtAuthInterceptor)
.addInterceptor(httpLoggingInterceptor)
.build()
val gson = GsonBuilder()
.setLenient().create()
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.build()
}
}
val nullOnEmptyConverterFactory = object : Converter.Factory() {
fun converterFactory() = this
override fun responseBodyConverter(type: Type, annotations: Array<out Annotation>, retrofit: Retrofit) = object : Converter<ResponseBody, Any?> {
val nextResponseBodyConverter = retrofit.nextResponseBodyConverter<Any?>(converterFactory(), type, annotations)
override fun convert(value: ResponseBody) = if (value.contentLength() != 0L) nextResponseBodyConverter.convert(value) else null
}
}
A sample how to call a request from the ViewModel
val query = RetrofitHelper.getInstance(applicationContext).create(AccessApi::class.java)
val call: Call<List<UsersType>> = query.getUsers()
call.enqueue(object : Callback<List<UsersType>> {
override fun onFailure(call: Call<List<UsersType>>, error: Throwable) {
}
override fun onResponse(
call: Call<List<UsersType>>,
response: Response<List<UsersType>>
) {
val result = response.body()
Log.i("Result", result?.size.toString())
if (result != null) {
// process data and send to a state for UI
}
}
UsersType – the data type for the result of data received
data class UsersType(
@SerializedName("id")
val id : Int,
@SerializedName("username")
val username : String,
@SerializedName("email")
val email : String,
@SerializedName("acc_token")
val acc_token : String
)
Now the PHP side. I am posting just snippets of various actions that would be most important for you to implement.
A great deal of the PHP logic was created by using the following as a reference. Good deal and explanations.
https://www.freecodecamp.org/news/php-jwt-authentication-implementation
This is the JWT.php – hold functions to encode and decode the JWT
<?php
class JWT
{
public function __construct(private string $key)
{
}
private function base64URLEncode(string $text): string
{
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($text));
}
public function encode(array $payload): string
{
$header = json_encode([
"alg" => "HS256",
"typ" => "JWT"
]);
$header = $this->base64URLEncode($header);
$payload = json_encode($payload);
$payload = $this->base64URLEncode($payload);
$signature = hash_hmac("sha256", $header . "." . $payload, $this->key, true);
$signature = $this->base64URLEncode($signature);
return $header . "." . $payload . "." . $signature;
}
public function decode(string $token): array
{
if (
preg_match(
"/^(?<header>.+)\.(?<payload>.+)\.(?<signature>.+)$/",
$token,
$matches
) !== 1
) {
throw new InvalidArgumentException("invalid token format");
}
$signature = hash_hmac(
"sha256",
$matches["header"] . "." . $matches["payload"],
$this->key,
true
);
$signature_from_token = $this->base64URLDecode($matches["signature"]);
if (!hash_equals($signature, $signature_from_token)) {
// throw new Exception("signature doesn't match");
throw new InvalidArgumentException("Invalid Signature");
}
$payload = json_decode($this->base64URLDecode($matches["payload"]), true);
return $payload;
}
private function base64URLDecode(string $text): string
{
return base64_decode(
str_replace(
["-", "_"],
["+", "/"],
$text
)
);
}
}
?>
ErrorCode
<?php
class ErrorCodes {
const NO_ERROR = -10;
const EMAIL_EXISTS = -1;
const PROBLEM_ADDING_CREDENTIALS = -2;
const ERROR_VALIDATING_CREDENTIALS = -3;
const CONNECTION_ISSUE = -4;
const FORM_DATA_ISSUE = -5;
const NO_ACCESS = -6;
const TOKEN_ERROR = -9;
}
?>
MyUtils
<?php
class MyUtils {
function getTodayDate(){
return date("Y-m-d");
}
function getTimestampFromDate($date){
return strtotime($date);
}
function findDifferenceInDaysBetweenTimeStamps($dateOne, $dateTwo){
return ($dateOne - $dateTwo) / (60 * 60 * 24);
}
function findDifferenceInMinutesBetweenTimeStamps($dateOne, $dateTwo){
return ($dateOne - $dateTwo) / (60);
}
function decryptPost($message){
$dataArr = explode(":", $message);
$str = "";
for($i = 0; $i < count($dataArr); $i++){
openssl_private_decrypt(base64_decode($dataArr[$i]), $test_decrypted, $_ENV['test_pk']);
$str .= $test_decrypted;
}
$str = mb_convert_encoding($str, "UTF-8");
$json = json_decode($str, true);
return $json;
}
function decryptString($message){
$dataArr = explode(":", $message);
$str = "";
for($i = 0; $i < count($dataArr); $i++){
openssl_private_decrypt(base64_decode($dataArr[$i]), $test_decrypted, $_ENV['test_pk']);
$str .= $test_decrypted;
}
$str = mb_convert_encoding($str, "UTF-8");
return $str;
}
function encryptPost($encryptedText){
$encrypted_arr = str_split($encryptedText, 53);
$encryptedStr = "";
for($i = 0; $i < sizeof($encrypted_arr); $i++){
openssl_public_encrypt($encrypted_arr[$i], $encrypted, $_ENV['test_pubk']);
if($i != 0){
$encryptedStr .= ":";
}
$encryptedStr .= base64_encode($encrypted);
}
return $encryptedStr;
}
}
?>
.env file used to store access variables
*DB_HOST=localhost
*DB_NAME=database_name
*DB_USER=admin
*DB_PASS=password
*SECRET_KEY=secret_key
*test_pubk=-----BEGIN PUBLIC KEY-----
public-key
public-key
-----END PUBLIC KEY-----
*test_pk=-----BEGIN PRIVATE KEY-----
private-key
private-key
private-key
private-key
private-key
private-key
private-key
private-key
-----END PRIVATE KEY-----
DotEnv.php – used to access .env variables
<?php
class DotEnv
{
/**
* The directory where the .env file can be located.
*
* @var string
*/
protected $path;
public function __construct(string $path)
{
if(!file_exists($path)) {
throw new \InvalidArgumentException(sprintf('%s does not exist', $path));
}
$this->path = $path;
}
//require_once("DotEnv.php");
//(new DotEnv(__DIR__ . '/.env'))->load();
public function load() :void
{
if (!is_readable($this->path)) {
throw new \RuntimeException(sprintf('%s file is not readable', $this->path));
}
$lines = file($this->path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$currentVal = "";
$currentName = "";
$stillProcessingLine = 0; // false = 0, true = 1
for($i = 0; $i < sizeof($lines); $i++){
if($stillProcessingLine){
$currentVal .= "\n".$lines[$i];
}
if(strpos($lines[$i], '*') === 0){
$stillProcessingLine = 0;
$arr = explode('=', $lines[$i], 2);
$currentName = $arr[0];
$currentVal = $arr[1];
$currentName = str_replace("*", "", $currentName);
}
if($i < sizeof($lines)-1){
if(strpos($lines[$i+1], "*") === false){
$stillProcessingLine = 1;
}else {
$stillProcessingLine = 0;
}
} else {
$stillProcessingLine = 0;
}
if($stillProcessingLine === 0){
$name = trim($currentName);
$value = trim($currentVal);
if (!array_key_exists($name, $_SERVER) && !array_key_exists($name, $_ENV)) {
putenv(sprintf('%s=%s', $name, $value));
$_ENV[$name] = $value;
$_SERVER[$name] = $value;
}
}
}
}
}
?>
Database Connection
<?php
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(403);
exit;
}
//load the .env file
require_once("DotEnv.php");
(new DotEnv(__DIR__ . '/../.env'))->load();
require_once("MyUtils.php");
require_once("ErrorCodes.php");
class Database
{
private ?PDO $conn = null;
public function __construct(
private string $host,
private string $name,
private string $user,
private string $password
) {
}
public function getConnection(): ?PDO
{
try {
if ($this->conn === null) {
$this->conn = new PDO("mysql:host={$this->host};dbname={$this->name}", $this->user, $this->password);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$this->conn->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
}
return $this->conn;
} catch (PDOException $e) {
echo "Connection failed: " . $e->getMessage();
return null;
}
}
}
?>
Sample of connecting to the database
$errorCodes = new ErrorCodes();
$database = new Database($_ENV['DB_HOST'], $_ENV['DB_NAME'], $_ENV['DB_USER'], $_ENV['DB_PASS']);
$con = $database->getConnection();
The process now would be to find the user in the database from login/register. Then implement the follow after you captured the user in a search query. As of now I just wanted to add a random tag in the JWT payload. You can put whatever you want in it.
if($find_user->rowCount() == 1){
$row = $find_user->fetch();
$current_time = $myUtils->getTimestampFromDate($myUtils->getTodayDate());
$JWTController = new JWT($_ENV['SECRET_KEY']);
$my_rand = sha1(uniqid().rand(1000000, 9999999));
$acc_payload = array(
'id' => $row['id'],
'username' => $row['username'],
'job' => $my_rand,
'iat' => $current_time
);
$acc_token = $JWTController->encode($acc_payload);
I would do the same for the refresh token then add both of these to the database in the users row.
Now for queries that validate the JWT. At any point where the data / token is invalid, you can send the
http_response_code(401);
This is what calls the Android Authenticator into action.
<?php
if (!isset($_SERVER["HTTP_AUTHORIZATION"])) {
http_response_code(403);
exit;
}
require_once('ErrorCodes.php');
$errorCodes = new ErrorCodes();
if (!preg_match("/Bearer\s(\S+)/", $_SERVER["HTTP_AUTHORIZATION"], $matches)) {
echo json_encode(array(
'id' => $errorCodes::NO_ACCESS
));
exit;
}
if(sizeof($matches) > 1){
//open database connection here
<?php
if (!isset($_SERVER["HTTP_AUTHORIZATION"])) {
http_response_code(403);
exit;
}
// echo json_encode(array(
// 'token' => $_SERVER["HTTP_AUTHORIZATION"] //"Type in your message here"
// ));
// exit;
require_once('ErrorCodes.php');
$errorCodes = new ErrorCodes();
if (!preg_match("/Bearer\s(\S+)/", $_SERVER["HTTP_AUTHORIZATION"], $matches)) {
echo json_encode(array(
'id' => $errorCodes::NO_ACCESS
));
exit;
}
if(sizeof($matches) > 1){
$id = $payload['id'];
$username = $payload['username'];
$acc_token = $matches[1];
//search the 3 parameters above in the database. if it returns a single row, then you have the right user and can proceed
if($find_user->rowCount() == 1){
$row = $find_user->fetch();
$current_time = $myUtils->getTimestampFromDate($myUtils->getTodayDate());
$timestamp = $row['timestamp'];
$diff = $myUtils->findDifferenceInMinutesBetweenTimeStamps($current_time, $timestamp);
if($diff > 30){
//this part is what calls the Android Retrofit Authenticator
http_response_code(401);
exit;
}
}
}
}
Do utilize the encryption / decryption in PHP
$myUtils = new MyUtils();
//get the encrypted message sent via POST
$data = json_decode(file_get_contents('php://input'));
$message = $data->{'message'};
//return decrypted message as JSON
$json = $myUtils->decryptPost($message);
Lastly the authenticator php file. It would be also like above code will it check the refresh token instead of the access token. After it validates its correct, then it will create a new access token ( also a new refresh token if out of date ) and send out through JSON just like what you register a new user.
<?php
if (isset($_SERVER["HTTP_AUTHORIZATION"])) {
require_once('ErrorCodes.php');
$errorCodes = new ErrorCodes();
if (!preg_match("/Bearer\s(\S+)/", $_SERVER["HTTP_AUTHORIZATION"], $matches)) {
echo json_encode(array(
'id' => $errorCodes::NO_ACCESS
));
exit;
}
if(sizeof($matches) > 1){
$JWTController = new JWT($_ENV['SECRET_KEY']);
$payload = null;
try{
$payload = $JWTController->decode($matches[1]);
} catch(Throwable $e){
}
if($payload != null){
$refresh_token = $matches[1];
$id = $payload['id'];
$username = $payload['username'];
//open database and search for the user row using the above 3 parameters
//check age of refresh token, if old create new
if($find_user->rowCount() == 1){
$current_time = $myUtils->getTimestampFromDate($myUtils->getTodayDate());
$row = $find_user->fetch();
$diff = $myUtils->findDifferenceInDaysBetweenTimeStamps($current_time, $payload['iat']);
if($diff > 4){
$my_token = sha1(uniqid().rand(1000000, 9999999));
$refresh_payload = array(
'id' => $row['id'],
'username' => $row['username'],
'job' => $my_token,
'iat' => $current_time
);
$refresh_token = $JWTController->encode($refresh_payload);
}
//lastly update database with timestamp, access and refresh tokens. then create a JSON array to send back to the authenticator
echo json_encode(array(
array(
'id' => $errorCodes::NO_ERROR,
'acc_token' => $row['acc_token'],
'refresh_token' => $row['refresh_token']
)
));
}
}
}
In the PHP I skipped a bunch of generic code to keep things short. I also didn’t show all my uses of error / exception identification. You can go through this code and find various spots to put them. Below shows an example of how I needed to send it so Android via the List<DataObject> would receive it. Its basically an array inside of an array.
echo json_encode(array(
array(
'id' => $errorCodes::TOKEN_ERROR
)
));
Hope this helps.