PHP Slim 4 - Authorize api request using firebase JWT token - php

I'm trying to verify the idToken provided from firebase javascript sdk with the Tuupola Jwt middleware for slim 4 but I always get a 401 error. This is the client code I'm using to get the token:
const provider = new firebase.auth.GoogleAuthProvider();
provider.addScope("profile");
provider.addScope("email");
firebase.auth().signInWithPopup(provider).then( (result) => {
console.log(result);
});
The auth flow will work correctly as expected and I'm able to pass the token into the Authorization header but I'm not able to verify it on the server where I'm using slim 4 for a Restful api.
I've read different question about this problem but none of this have helped me to solve this problem.
here is my middleware implementation:
use Tuupola\Middleware\CorsMiddleware;
use Tuupola\Middleware\JwtAuthentication;
use Slim\App as App;
return function(App $app) {
$app->add(new Tuupola\Middleware\CorsMiddleware([
"origin" => ["chrome-extension://oegddbimpfdpbojkmfibkebnagidflfc"],
"methods" => ["GET", "POST", "OPTIONS"],
"headers.allow" => ["Authorization"],
"headers.expose" => [],
"credentials" => true,
"cache" => 86400
]));
// $rawPublicKeys = file_get_contents('https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com');
// $keys = json_decode($rawPublicKeys, true);
$keys = file_get_contents('https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com');
$app->add(new Tuupola\Middleware\JwtAuthentication([
"algorithm" => ["RS256"],
"header" => "X-Authorization",
"regexp" => "/Bearer\s+(.*)$/i",
"secret" => $keys,
"secure" => false,
"after" => function ($response, $arguments) {
return $response->withHeader("X-Brawndo", "plants crave"); //this is only for test
}
]));
};
and this is what I have inside my index.php file where slim app is running
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Routing\RouteCollectorProxy;
use Slim\Routing\RouteContext;
use Slim\Factory\AppFactory;
use Tuupola\Middleware\CorsMiddleware;
require_once __DIR__.'/vendor/autoload.php';
$app = AppFactory::create();
$authMiddleware = require_once __DIR__.'/middleware.php';
$authMiddleware($app);
$app->get('/keygen', function(Request $request, Response $response, $args){
$password = bin2hex(random_bytes(3));
$response->getBody()->write( json_encode(['generated_password' => $password]) );
return $response->withHeader('Content-Type','application/json');
});
$app->add(new Tuupola\Middleware\CorsMiddleware([
"origin" => ["*"],
"methods" => ["GET", "POST", "OPTIONS"],
"headers.allow" => ["Authorization"],
"headers.expose" => [],
"credentials" => true,
"cache" => 86400
]));
$app->run();
What I want to achive is to authenticate each request made from the client to the api using the firebase idToken provided after client login. When a request is made, the middleware will verify the token and then authorize the user or not to use the endpoint.
Is possible to fix this?

After a lot of debug I've found and solved the problem. In my client code I was using the wrong idToken as Authorization: Bearer and also the header sended to the server was mismatching the middelware configuration, in my axios requests I was sending the X-Authorization header instead of Authorization. To get the correct token to use I've called firebase.auth().onAuthStateChanged( (user) =>{...}) method and when the user object become available I've called the getIdToken() method. This operation return the correct JWT token to use with the middleware to authenticate the requests.

Related

Firestore storage request in the name of an application user (PHP)

I'd like to authenticate our application user against Firebase/Firestore and then make a request to the storage as this user (i.e. not as the service account).
I know of two methods for the authentication:
Simple HTTP Request
$client = new GuzzleHttp\Client();
$responee = $client->request(
'POST',
'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=' . $key,
[
'headers' => [
'content-type' => 'application/json',
'Accept' => 'application/json'
],
'body' => json_encode([
'email' => $email,
'password' => $password,
'returnSecureToken' => true
]),
'exceptions' => false
]
);
Kreait SDK
$userRecord = $auth->verifyPassword($email, $password);
What I don't know is how to use this information to make a request to the storage.
Google Cloud Firestore SDK
StorageClient accepts a config key credentialsFetcher but I don't know how to use it. It accepts any object that implements FetchAuthTokenInterface. I've toyed with those that exist, even tried implementing my own that just passes on the idToken from the Simple HTTP Request method. No luck.
$credentialsFetcher = new myFetchAuthTokenImplementation($idToken);
$storage = new StorageClient([
'credentialsFetcher' => $credentialsFetcher,
]);
$bucket = $storage->bucket('my_bucket');
$object = $bucket->object('file_backup.txt');
print $object->downloadAsString();
use Google\Auth\FetchAuthTokenInterface;
class myFetchAuthTokenImplementation implements FetchAuthTokenInterface
{
private $token;
public function __construct(string $token)
{
$this->token = [
'access_token' => $token,
];
}
public function fetchAuthToken(callable $httpHandler = null)
{
return $this->token;
}
public function getCacheKey()
{
return null;
}
public function getLastReceivedToken()
{
return $this->token;
}
}
Kreait SDK
It seems it can fetch information from storage but only using the service account. Not my application user.
$firebaseFactory = (new Factory)->withServiceAccount(__DIR__.'/google-service-account.json');
$storage = $firebaseFactory->createStorage();
$imageUrl = $storage->getBucket()
->object('file_backup.txt')
I would need to re-initialize the $firebaseFactory with the application user record, something like this fictitious method $firebaseFactory = (new Factory)->withApplicationUser($userRecord);
Although I would like to use some SDK, any solution is fine, even with simple HTTP requests.
I would probably be able to implement this using the Google JavaScript SDK but I'd like to stick to PHP.
Your help is greatly appreciated.
As far as I know, the Kreait PHP SDK wraps the Google Cloud Storage REST API. If it does, it always accesses Storage with Administrative credentials, and there is no way to access it as a Firebase Authentication user account, nor to enforce the security rules for a specific user.
To access Cloud Storage as a Firebase Authentication user, you will have to authenticate client-side, and pass the resulting ID token to an SDK/API that enforces Firebase security rules for specific users. This means you'll have to use one of the client-side Firebase SDKs for accessing Cloud Storage, as there currently is no public REST API that exposes this functionality.

How to send a request to another controller in Laravel using Guzzle

I am trying to send a POST request using Guzzle to a route defined in my routes/web.php from a model. Both the model and the controller are defined in the same Laravel application. The controller action linked to the route returns a JSON response and works fine when called from javascript using Ajax. However, when I try to do this using Guzzle, I have the following error:
GuzzleHttp \ Exception \ ClientException (419)
Client error: `POST https://dev.application.com/login` resulted in a `419 unknown status` response
When searching for a solution, I read that it may be caused by a missing csrf token, so I added it to my reuqest, but I still get the same error.
Here's the model code that uses Guzzle to send the request:
$client = new Client();
$response = $client->post(APPLICATION_URL.'login', [
'headers' => [
'X-CSRF-Token' => csrf_token()
],
'form_params' => [
'socialNetwork' => 'L',
'id_token' => $id
],
]);
APPLICATION_URL is simply the base URL of the application, starting with https://.
Am I missing something? Thanks in advance!
Don't send requests internally in your app, forward the call by dispatching post requests to routes instead
This method seems faster than using an HTTP client library like Guzzle
Your code should look something like this
$request = Request::create(APPLICATION_URL . 'login', 'POST', [
'socialNetwork' => 'L',
'id_token' => $id
]);
$request->headers->set('X-CSRF-TOKEN', csrf_token());
$response = app()->handle($request);
$response = json_decode($response->getContent(), true);
Update
You have to manually handle the response from internally dispatched routes, here's an example to get started
web.php
use Illuminate\Http\Request;
Route::get('/', function () {
$request = Request::create('/test', 'POST', ['var' => 'bar']);
$request->headers->set('X-CSRF-TOKEN', csrf_token());
$response = app()->handle($request);
$responseContent = json_decode($response->getContent(), true);
return $responseContent;
});
Route::post('test', function () {
$upperCaseVar = strtoupper(request()->var);
return response(['foo' => $upperCaseVar]);
});
Access / route by GET request and get response from /test as if it's POST request
Result
{
"foo": "BAR"
}
Hope this helps

Google PHP Api Client - I keep getting Error 401: UNAUTHENTICATED

I've been struggling with this for hours now, if not days and can't seem to fix it.
My Requests to Cloud Functions are being denied with error code: 401: UNAUTHENTICATED.
My Code is as follow:
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . FIREBASE_SERIVCE_PATH);
$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(Google_Service_CloudFunctions::CLOUD_PLATFORM);
$httpClient = $client->authorize();
$promise = $httpClient->requestAsync("POST", "<MyCloudFunctionExecutionUri>", ['json' => ['data' => []]]);
$promise->then(
function (ResponseInterface $res) {
echo "<pre>";
print_r($res->getStatusCode());
echo "</pre>";
},
function (RequestException $e) {
echo $e->getMessage() . "\n";
echo $e->getRequest()->getMethod();
}
);
$promise->wait();
I'm currently executing this from localhost as I'm still in development phase.
My FIREBASE_SERIVCE_PATH constant links to my service_account js
My Cloud Function index.js:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
// CORS Express middleware to enable CORS Requests.
const cors = require('cors')({
origin: true,
});
exports.testFunction = functions.https.onCall((data, context) => {
return new Promise((resolve, reject) => {
resolve("Ok:)");
});
});
// [END all]
My Cloud Function Logs:
Function execution took 459 ms, finished with status code: 401
What am I doing wrong so I get Unauthenticated?
PS: My testFunction works perfectly when invoked from my Flutter mobile app who uses: https://pub.dartlang.org/packages/cloud_functions
Update:
I have followed this guide: https://developers.google.com/api-client-library/php/auth/service-accounts but in the "Delegating domain-wide authority to the service account" section, it only states If my application runs in a Google Apps domain, however I wont using Google Apps domain, and plus I'm on localhost.
First of all thanks to Doug Stevenson for the answer above! It helped me to get a working solution for callable functions (functions.https.onCall).
The main idea is that such functions expect the auth context of the Firebase User that already logged in. It's not a Service Account, it's a user record in the Authentication section of your Firebase project. So, first, we have to authorize a user, get the ID token from response and then use this token for the request to call a callable function.
So, below is my working snippet (from the Drupal 8 project actually).
use Exception;
use Google_Client;
use Google_Service_CloudFunctions;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Promise;
use GuzzleHttp\RequestOptions;
$client = new Google_Client();
$config_path = <PATH TO SERVICE ACCOUNT JSON FILE>;
$json = file_get_contents($config_path);
$config = json_decode($json, TRUE);
$project_id = $config['project_id'];
$options = [RequestOptions::SYNCHRONOUS => TRUE];
$client->setAuthConfig($config_path);
$client->addScope(Google_Service_CloudFunctions::CLOUD_PLATFORM);
$httpClient = $client->authorize();
$handler = $httpClient->getConfig('handler');
/** #var \Psr\Http\Message\ResponseInterface $res */
$res = $httpClient->request('POST', "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=<YOUR FIREBASE PROJECT API KEY>", [
'json' => [
'email' => <FIREBASE USER EMAIL>,
'password' => <FIREBASE USER PASSWORD>,
'returnSecureToken' => TRUE,
],
]);
$json = $res->getBody()->getContents();
$data = json_decode($json);
$id_token = $data->idToken;
$request = new Request('POST', "https://us-central1-$project_id.cloudfunctions.net/<YOUR CLOUD FUNCTION NAME>", [
'Content-Type' => 'application/json',
'Authorization' => "Bearer $id_token",
], Psr7\stream_for(json_encode([
'data' => [],
])));
try {
$promise = Promise\promise_for($handler($request, $options));
}
catch (Exception $e) {
$promise = Promise\rejection_for($e);
}
try {
/** #var \Psr\Http\Message\ResponseInterface $res */
$res = $promise->wait();
$json = $res->getBody()->getContents();
$data = json_decode($json);
...
}
catch (Exception $e) {
}
Callable functions impose a protocol on top of regular HTTP functions. Normally you invoke them using the Firebase client SDK. Since you don't have an SDK to work with that implements the protocol, you'll have to follow it yourself. You can't just invoke them like a normal HTTP function.
If you don't want to implement the protocol, you should instead use a regular HTTP function, and stop using the client SDK in your mobile app.

How can I get user_id with access token in laravel passport api?

I am creating mobile application and for login I am using oauth.
For url like http://localhost/darkhwast/public/oauth/token
it gives me output json as below:
{
"token_type": "Bearer",
"expires_in": 31536000,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjIyZWQ5YWFjY2U4MDVjYzc4NzUzMWM5NjVkMjdiZDZkNTIwOTQ5NGYxMTllN2Q3YWYyZDQyYWI5MjRjZjYyNTk0ZjhiNTBjNzMyNWMyYjlkIn0.eyJhdWQiOiIyIiwianRpIjoiMjJlZDlhYWNjZTgwNWNjNzg3NTMxYzk2NWQyN2JkNmQ1MjA5NDk0ZjExOWU3ZDdhZjJkNDJhYjkyNGNmNjI1OTRmOGI1MGM3MzI1YzJiOWQiLCJpYXQiOjE0OTgxMzQ1MjAsIm5iZiI6MTQ5ODEzNDUyMCwiZXhwIjoxNTI5NjcwNTIwLCJzdWIiOiIzIiwic2NvcGVzIjpbIioiXX0.CdhOhJ_6wb_KphCbnQEwI8iw94MmvlwCnG8PGPSEcm-YoXeaw2WoXiYRizbkhiXIP84BReRVIXxI-Rug6GUWwT1W8cjrvJinQT2UghCcUMqQ6nQlBingKUUlqyaww5rbcIj6RNDVuRtGnVhpSl6g1wsBz534GmNJyaY5F7t9ZJlf4Q80Cay9mV_YcLVnlOTZqTfGaujo6OM24pG6EoCiyOEF-4Vyd4Naov5O_AswuouCT7kuFdMbNYwNu6hB9_swf7yek_-shqgPk3AGJsnkavCI5Mgj3xQdhhtxoy6IxFcebBZ1iI6V_yd0-UDzHHsVZf2bVk4Hx0j84vA4ZkXXDkc85Lxqpafd31i51eXGPaW308VH2EPV9QwNOxNwNF9nl6uAlkcvfhfNBnNx_QGMALmyuNQf1CXY_rkA72pYkekTe4LQGX48dpIJUnFgnj8Jwsfjrda1D6_N5JvrnbvJkVZbCCgOD9vhJUGnVw6PyEcXldWHiW7EJZkAX9XYB571vzN__qkbM--UpU1fMY13HvWe6qTRSPt4NdZudg1zmQOCn0TpvonP4FGGeB_ldEA488LASAAtQdwHQryw4oZvcb1BSDrvw7IVpGva5ky8aIoeQIORPn2Ehg_I1X9q1Yy3UI8iBPQBWgAnGnyoPYhtIvXYNWLUQJaaocLe3eE7osU",
"refresh_token": "Mrd+2d8KbLMMbT9bRbxS4YjGUp2nCB092HLaJAxnNIAabDTDfOncYFNAMiCsp/Lc96cLfF8/nE4vAq4DNnpbM8ITCswQLo4/0cY0+e/wgdAhv5ZguPOXmVsOakRT1vymWVj+DH7m1ZlynwD+hMiwVP439iEP66qS6UZqZQ+k+s4SVfMFOE0MUynEFxb27Z8zgeV8Tbw/wA67QlQxXYWWuRL809QQQsFYnmttL71nYqn23TN12iV3/Fmol8S4Kc4ODk3eSFd8C5UUPbTYSdPDOOUUt+v/yrwJgIuZG5PSybG27iPKKkMbI3JgLQc25cAsJUHtkciAn3BVnEV4K2C6hwRM91vGvljB2siblgGw3RooHdYkfZQgCP8zrBvLY9daDKFEuP1QCk8mfpI1kAhhqe3pgDpbIVDlNau+SQXiGMizcMKg/YhnLJpNlU8eqIksLIEEQmFUBfizPmEpyb+KqqhpbfPciGU4LgmwZknferEuAqLS3yzrfWkML5CGYALVWrmo8r2va/qn7yEWY3lYcpcwSXPQiG3TkNKaupfKPQ/i/+aDRb0MOEc68rRkBrSFcOhR0HUgXg+Ev1mNqwuKyu3oGJwYSEGuGEggu/0Ekd1Qem8t7WZAQ245YEj8DpiJcFLX7E9fL1Bo5yuBn9vzTcqRa+kKC7725ayaX3nzxhY="
}
But how can I get user_id in response?
Your OAuth server should have an authenticated API endpoint (using passport), something like /api/user or /api/me. You use the provided access_token to authenticate yourself to retrieve the authenticated user details:
$response = $client->request('GET', '/api/user', [
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $access_token,
],
]);
$body = (string) $response->getBody();
$remoteUser = json_decode($body);
Once you've retrieved the user details, you should have enough to register the user account locally within the client app's database (if required).
You should encrypt the tokens before storing them on the user record.
Use encrypt() and decrypt() Laravel's reversible encryption helpers, so it's stored safely but can be decrypted for use later.
I think changing in laravel package would not be good solution.
if we have access_token then we can get user detail by $request->user() . Example:
in api.php If we write
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
});
Same like above in any controller if we request API with minimum required headers i.e Authorization: Bearer access_token and Accept : application/json we can get user detail.
Like in my case(with Header Authorization: Bearer access_token and Accept : application/json):
// with route of middleware: middleware('auth:api')
class UserController extends BaseController
{
public function index()
{
return Auth::user(); // here I got all user detail.
}
}
Hope it helps someone.

Basic auth with Slim no response

I am implementing a basic auth with Slim and REST. I have installed the basic auth via Composer and used the below code.
<?php
require 'confing.php';
require 'Slim/Slim.php';
\Slim\Slim::registerAutoloader();
$app = new \Slim\Slim;
$app->add(new \Slim\Middleware\HttpBasicAuthentication([
"path" => "/admin", /* or ["/admin", "/api"] */
"realm" => "Protected",
"users" => [
"root" => "t00r",
"user" => "passw0rd"
],
"callback" => function ($request, $response, $arguments) {
print_r($arguments);
}
]));
$app->get('/getLaboorState/:laboor_id', function($laboor_id) use ($app) {
$db =getDB();
$sql="SELECT status FROM laboor WHERE laboor_id='".$laboor_id."'";
$stmt = $db->query($sql);
$items = $stmt->fetchAll();
echo json_encode($items);
});
$app->run();
?>
When I am trying now to connect the /getLaboorState with Postman it returns nothing. I used same username and password in postman and nothing shows, but when I take the basic auth it works fine.
Other questions is, after implement the basic auth, how can I restrict all slim api to go throw each api before run the query?
This is a pic from Postman:
Note: then I want to use the API with AJAX.
you need to use $authenticate($app) to restrict all slim api to go throw each api before run the query
$app->get('/profile(/)(:id)', $authenticate($app), function($laboor_id) use ($app) {
//Your logic here
})->name('profile');
$authenticate = function ($app) {
return function () use ($app) {
//your logic here
if (!isset($_SESSION['ID'])) {
$app->redirect($app->urlFor('loginpage'));
}
};
};
Use bellow code to display the exact error coming while calling Ajax request
header('Access-Control-Allow-Origin: *');
ini_set('display_errors', 1);
error_reporting(E_ALL);
Hope this helps, Accept the answer if it works.. or comment
You have configured two users:
Username root with password t00r
Username user with password passw0rd
According to your screenshot you are trying to use username t00r with password passw0rd. This does not exist in your configuration. Use one of the username password combinations mentioned above.

Categories