- Laravel firebase / PHP JWT token verification
- 2. Installation
- 3. Use
- 3.1. Issuing token
- 3.2. Verify the token
- 4. Comprehensive application
- 4.1 usage scenario
- 4.2 use
- Verify Firebase auth JWT with PHP Laravel
- Overall flow
- TL;DR
- Install firebase/php-jwt package
- FirebaseToken class
- Fetch public keys for verifying JWT
- Decode and verify JWT
- Let’s use it!
- About Me
Laravel firebase / PHP JWT token verification
The jwt here is somewhat different from the jwt in the previous article. The previous one is user based interface authentication, that is, interface login is required. This jwt is based on a unique ID, such as a unique ID of a mobile device or a mobile phone number, which can be used as a unique ID to complete token issuance and authentication.
2. Installation
composer require firebase/php-jwt
3. Use
3.1. Issuing token
public function createToken() < $key = '344'; //key, unique identifier $time = time(); //current time $token = [ 'iat' =>$time, //Time filed 'nbf' => $time , //(Not Before): it can only be accessed after a certain time point. For example, setting time+30 means that it can only be used after 30 seconds of the current time 'exp' => $time+7200, //Expiration time, 2 hours set here 'data' => [ //Customize information, do not define sensitive information 'device_id' => 'asdfghj', ] ]; $token = JWT::encode($token, $key,'HS256'); //Issue token $data = ['error'=>0,'mgs'=>'success','data'=>['token'=>$token]]; return json_encode($data); >
3.2. Verify the token
// $token: issued token public function verifyToken($token) < $key = '344'; //The key must have the same unique ID as when it was signed and issued try < JWT::$leeway = 60;//Subtract 60 from the current time to make room for the time $decoded = JWT::decode($token, $key, ['HS256']); //HS256 mode, which corresponds to the time of issuance $arr = (array)$decoded; print_r($arr); >catch(\Firebase\JWT\SignatureInvalidException $e) < //Incorrect signature echo $e->getMessage(); >catch(\Firebase\JWT\BeforeValidException $e) < // The signature can only be used after a certain point in time echo $e->getMessage(); >catch(\Firebase\JWT\ExpiredException $e) < // token expiration echo $e->getMessage(); >catch(Exception $e) < //Other errors echo $e->getMessage(); > >
4. Comprehensive application
4.1 usage scenario
In the project, the app interface needs interface verification without logging in. The app side can provide a unique ID identifying the device, generate a token through this ID, and complete interface verification through this ID and token.
4.2 use
composer require firebase/php-jwt
One is used to generate the token and one is used to refresh the token
key: the encrypted string of the unique ID of the device. It also needs to be provided in the interface request and used in the verification
/** * Generate and return token * @return bool|string */ public function getToken($param) < $deviceId = isset($param['device_id']) ? $param['device_id'] : ''; //device_id as unique identification if(empty($deviceId))< return $this->responseInvalidParams('device_id is empty'); > $key = encrypt($deviceId); //key $time = time(); //current time $overtime = 7200; $token = [ 'iat' => $time, //Time filed 'nbf' => $time , //(Not Before): it can only be accessed after a certain time point. For example, setting time+30 means that it can only be used after 30 seconds of the current time 'exp' => $time+$overtime, //Expiration time, 2 hours set here ]; if(!$token = JWT::encode($token, $key,'HS256'))< \Log::error('app api operation failed: key='.$key.', token='.$token); return response()->json(['error'=>1,'msg'=>'operation failed','data'=>[]]); > return response()->json(['error'=>0,'msg'=>'ok','data'=>['key'=>$key,'token'=>$token,'expire'=>$overtime]]); > /** * Regenerate and return token * @return bool|string */ public function refreshToken($key) < if(empty($key))< return $this->responseInvalidParams('key is empty'); > $newKey = encrypt(decrypt($key));//Regenerate key $time = time(); //current time $overtime = 7200; $token = [ 'iat' => $time, //Time filed 'nbf' => $time , //(Not Before): it can only be accessed after a certain time point. For example, setting time+30 means that it can only be used after 30 seconds of the current time 'exp' => $time+$overtime, //Expiration time, 2 hours set here ]; if(!$token = JWT::encode($token, $newKey,'HS256'))< \Log::error('app api operation failed: key='.$newKey.', token='.$token); return response()->json(['error'=>1,'msg'=>'operation failed','data'=>[]]); > return response()->json(['error'=>0,'msg'=>'ok','data'=>['key'=>$key,'token'=>$token,'expire'=>$overtime]]); >
The middleware enables the interface to verify the token every request
php artisan make:middleware VerifySign //Generation Middleware
0,'msg'=>'','data'=>[]]; $key = $request->header('key'); $token = $request->header('token'); if(empty($key) || empty($token))< $result['error'] = 403; $result['msg'] = 'headers param error'; \Log::error('app api headers param error: key='.$key.', token='.$token); return response()->json($result); > try < JWT::$leeway = 60;//Subtract 60 from the current time to make room for the time if(!$rs = JWT::decode($token, $key, ['HS256']))/HS256>json($result); > > catch(\Firebase\JWT\SignatureInvalidException $e) < //Incorrect signature $error = $e->getMessage(); >catch(\Firebase\JWT\BeforeValidException $e) < // The signature can only be used after a certain point in time $error = $e->getMessage(); >catch(\Firebase\JWT\ExpiredException $e) < // token expiration $error = $e->getMessage(); >catch(Exception $e) < //Other errors $error = $e->getMessage(); > if(!empty($error))< $result['error'] = 403; $result['msg'] = $error; \Log::error('app api error: '.$error); return response()->json($result); > return $next($request); > >
④,app/Http/Kernel. List of routing middleware for PHP applications
protected $routeMiddleware = [ //. 'app.sign' => \App\Http\Middleware\VerifySign::class, ];
Route::get('auth/token', 'App\System\UserController@getToken');//Get token //You need to verify the route of the token Route::group(['namespace' => 'App','middleware' => ['app.sign']], function () < Route::get('/auth/refresh/token', 'System\UserController@refreshToken');//Refresh token Route::get('/home/index', 'Home\IndexController@index');//home page //. >);
Call the previously encapsulated operation token method
//Get token public function getToken(Request $request) < return $this->biz->getToken($request->all()); > //Refresh token public function refreshToken(Request $request) < return $this->biz->refreshToken($request->header('key')); >
Request to generate token: http://xx.com/api/auth/token?device_id=15616513
Others define app The routing request interface in sign middleware needs to carry key and token
Reproduced at: https://www.cnblogs.com/mg007/p/11293939.html
Posted by LuiePL on Sat, 08 Jan 2022 19:41:17 -0800
Verify Firebase auth JWT with PHP Laravel
A few months ago, I wrote about how to verify Firebase auth JWT using Ruby. Similarly, Firebase doesn’t provide an official SDK for PHP. In this post, I will walk you through how to decode and verify Firebase JWT using PHP. It will use a few Laravel classes for caching, http client, and string manipulations, but the approach I will show here is framework-agnostic.
Overall flow
- The client app retrieves the Firebase ID Token (JWT) and send it to the backend server through Authorization HTTP header
- The backend server decodes and verifies the JWT following this guide
I’m not going to cover how to retrieve the token on the client side since it’s very straightforward. This post is focused on decoding and verifying JWT on the backend.
TL;DR
I created the FirebaseToken class with verify() method that verifies JWT and returns the docoded payload which contains the authenticated user data. You can find the full codebase here. Let me explain how it works step by step.
Install firebase/php-jwt package
First, let’s install firebase/php-jwt which we will use for decoding and verifying JWT.
$ composer require firebase/php-jwt
FirebaseToken class
Next, create the FirebaseToken class. The constructor takes the $token which is the actual JWT.
/** * Firebase ID token. * * @var string */ private string $token; /** * @param string $token */ public function __construct(string $token) < $this->token = $token; >
Fetch public keys for verifying JWT
In the verify() method, we will use the JWT::decode() that firebase/php-jwt provides. It takes public keys as the second argument which will be used for verifying token was signed by the right private key.
/** * Verify the ID token and return the decoded payload. * * @param string $projectId * @return object * @throws UnexpectedValueException|Exception */ public function verify(string $projectId): object < $keys = $this->getPublicKeys(); $payload = JWT::decode($this->token, $keys, self::ALLOWED_ALGOS); $this->validatePayload($payload, $projectId); return $payload; >
Grab the public key from here and pass it to JWT::decode() method. As you can see, it caches the response (i.e. public keys) to avoid downloading it every time. For the cache TTL, use the max-age in the Cache-Control header of the response.
/** * Fetch JWT public keys. * * @return array * @throws Exception */ private function getPublicKeys(): array < if (Cache::has(self::CACHE_KEY)) < return Cache::get(self::CACHE_KEY); >$response = Http::get(self::PUBLIC_KEY_URL); if (!$response->successful()) < throw new \Exception('Failed to fetch JWT public keys.'); >$publicKeys = $response->json(); $cacheControl = $response->header('Cache-Control'); $maxAge = Str::of($cacheControl)->match('/max-age=(\d+)/'); Cache::put(self::CACHE_KEY, $publicKeys, now()->addSeconds($maxAge)); return $publicKeys; >
Decode and verify JWT
Now, let’s decode and verify the JWT calling the JWT::decode() method.
$payload = JWT::decode($this->token, $keys, self::ALLOWED_ALGOS);
The third argument is an array of signing algorithms used in the JWT. Firebase uses RS256 for signing JWT.
/** * The list of allowed signing algorithms used in the JWT. * * @var array */ const ALLOWED_ALGOS = ['RS256'];
We need to verify the header and payload following Firebase’s official guide. JWT::decode() verifies most of the attributes (see here for details), but we need to verify aud , iss , and sub manually like below.
/** * Validate decoded payload. * * @param object $payload * @param string $projectId Firebase project id * @return void * @throws UnexpectedValueException * * @see https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library */ private function validatePayload(object $payload, string $projectId): void < if ($payload->aud !== $projectId) < throw new UnexpectedValueException("Invalid audience: aud>"); > if ($payload->iss !== "https://securetoken.google.com/") < throw new UnexpectedValueException("Invalid issuer: iss>"); > // `sub` corresponds to the `uid` of the Firebase user. if (empty($payload->sub)) < throw new UnexpectedValueException('Payload subject is empty.'); >>
Let’s use it!
Done! Finally, we can use the FirebaseToken class to decode and verify Firebase JWT.
// Retrieve Authorization header $token = $request->bearerToken(); $payload = (new FirebaseToken($token))->verify( config('services.firebase.project_id') ); $payload->user_id;
I added the following config in config/services.php .
'firebase' => [ 'project_id' => env('FIREBASE_PROJECT_ID'), ]
You can use it for your custom auth guard in your Laravel application. The full codebase can be found here.
If you’ve got any questions or comments, please comment in this Twitter thread!
About Me
I am a software engineer from Japan and currently based in Singapore. Over the past 5 years, I’ve been creating e-commerce and FinTech web applications using Laravel, Angular, and Vue.js. Making clean, maintainable, and scalable software with agile methodology is one of my biggest passions. Im my spare time, I play the bass and enjoy DIY.