I'm using the chadicus/slim-oauth2 collection for slimframework 3.
This is my code atm (running on Apache2):
<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
use \Slim\Middleware\HttpBasicAuthentication\PdoAuthenticator;
use Chadicus\Slim\OAuth2\Http\RequestBridge;
use Chadicus\Slim\OAuth2\Http\ResponseBridge;
use Chadicus\Slim\OAuth2\Middleware;
use OAuth2;
use OAuth2\GrantType;
use OAuth2\Storage;
use Slim;
require '../vendor/autoload.php';
define(KUNDEN,'kunden');
define(VERTRAEGE,'vertraege');
define(ADRESSE,'adresse');
$config['displayErrorDetails'] = true;
$config['addContentLengthHeader'] = false;
$config['db']['host'] = "localhost";
$config['db']['user'] = "vv";
$config['db']['pass'] = "vv";
$config['db']['dbname'] = "vv";
$storage = new Storage\Memory(
[
'client_credentials' => [
'administrator' => [
'client_id' => 'administrator',
'client_secret' => 'password',
'scope' => 'superUser',
],
'foo-client' => [
'client_id' => 'foo-client',
'client_secret' => 'p4ssw0rd',
'scope' => 'basicUser canViewFoos',
],
'bar-client' => [
'client_id' => 'foo-client',
'client_secret' => '!password1',
'scope' => 'basicUser',
],
],
]
);
$server = new OAuth2\Server(
$storage,
[
'access_lifetime' => 3600,
],
[
new GrantType\ClientCredentials($storage),
]
);
$app = new \Slim\App(["settings"=>$config]);
$authMiddleware = new Middleware\Authorization($server, $app->getContainer());
$container=$app->getContainer();
$container['db'] = function ($c) {
$db = $c['settings']['db'];
$pdo = new PDO("mysql:host=" . $db['host'] . ";dbname=" . $db['dbname'],
$db['user'], $db['pass']);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
return $pdo;
};
$app->post('/token', function ($psrRequest, $psrResponse, array $args) use ($app, $server) {
//create an \OAuth2\Request from the current \Slim\Http\Request Object
$oauth2Request = RequestBridge::toOAuth2($psrRequest);
//Allow the oauth2 server instance to handle the oauth2 request
$oauth2Response = $server->handleTokenRequest($oauth2Request);
//Map the oauth2 response into the slim response
//print_r($server['storage']);
return ResponseBridge::fromOAuth2($oauth2Response);
});
$app->get('/'.KUNDEN, function (Request $request, Response $response) {
$query=$this->db->prepare("Select * from customer");
$query->execute();
return $response->withJson($query->fetchAll());
})->add($authMiddleware);
$app->run();
If i now request access to /token with postman and the administrator/password credentials, I get a token back. But if I try to open /kunden with this token I get: "Invalid token".
Get token back from server
Invalid token
I'm not sure if the token were stored in the memory correctly. And, to be honest, I have not many experience with oAuth2.
Can anyone give me a push in the right direction. I need a hint, where I have to search on the internet. Because "Slimframework oauth2 invalid token" are not the right keywords for google :-/
Thanks in advance!
Franz
I had the same problem and it was that i just didn't know how to work with OAuth2 Server PHP.
You must create the OAuth2 tables on your database as you can read here: https://bshaffer.github.io/oauth2-server-php-docs/cookbook/
Then you can create your users in the database and use PDO to authenticate and save the user token:
$pdo = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass);
$storage = new Storage\Pdo($pdo);
$server = new OAuth2\Server(
$storage,
[
'access_lifetime' => 3600,
],
[
new GrantType\ClientCredentials($storage),
new GrantType\AuthorizationCode($storage),
]
);
For anyone else who stumbles upon this question from search engines.
First, you must understand that the "Memory" storage in OAuth2 is not persistent, meaning that when you create a token, it will not be saved anywhere. It's meant to show you that the token creation part of the mechanism is working.
Second, you must use another storage (PDO or Redis) if you want to authenticate clients to your routes. This is fairly simple to do with Redis. You just install redis-server on your server, secure it, and then implement it in your code. For an easy-to-use implementation of Redis, check the composer package predis/predis.
For more information how to implement OAuth2 with Redis, visit: https://bshaffer.github.io/oauth2-server-php-docs/storage/redis/
Have a nice day :)
Related
i use passport in laravel and after user verification i want to generate a token for them with refresh token . for get refresh token i have to send curl request with password grant_type . after generate access token i want to get Password Grant Clientfrom database and pass it to my curl body . i fount this code snippet:
$tokenId = (new Parser(new JoseEncoder()))->parse($token)->claims()->get('jti');
$client = \Laravel\Passport\Token::find($tokenId)->client;
and the problem result of client variable is Personal Access Client not Password Grant Client and get this error because of its personal type:
{
"error": "invalid_client",
"error_description": "Client authentication failed",
"message": "Client authentication failed"
}
this is my code:
$token = $user->createToken(Config::get('auth.guards.api.token_name'))->accessToken;
$tokenId = (new Parser(new JoseEncoder()))->parse($token)->claims()->get('jti');
$client = \Laravel\Passport\Token::find($tokenId)->client;
$http = new \GuzzleHttp\Client();
try {
$response = $http->request('POST', url("/oauth/token"), [
'form_params' => [
'grant_type' => 'password',
'client_id' => $oClient->id,
'client_secret' => $oClient->secret,
'username' => $username,
'password' => $password,
'scope' => '*',
],
]);
} catch (\Exception $exception) {
}
how can i deal with this?
For obtaining the client_id and client_secret for Password Grant Client you need to run the following command on your authorization server (OAuth server) as stated here https://laravel.com/docs/9.x/passport#creating-a-password-grant-client
php artisan passport:client --password
The above command is not necessary to run if you already ran passport:install. The easiest way is to check your oauth_clients table for the column password_client there should be a row that has this value set to 1 (enabled).
It seems from your question that you are trying to obtain the client_id and client_secret programmatically from your client. This is not the correct way of doing it.
Basically after you run the above command to generate your client_id and client_secret you need to hard code them in your .env and use them in you CURL such as:
$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
'grant_type' => 'password',
'client_id' => env('OAUTH_CLIENT_ID'),
'client_secret' => env('OAUTH_CLIENT_SECRET'),
'username' => $username,
'password' => $password,
'scope' => '*',
]);
return $response->json();
You can obtain your client_id and client_secret from the oauth_clients table. Just make sure to copy the values where the password_client column is set to 1.
There should not be any security concern if your client is storing these credentials in the backend and doing the CURL from the backend.
In the case you are trying to do this from a mobile app and you might not have a way to securely store the client_id and client_secret. In this case you should not be using the Password Grant Client flow but instead the Authorization Code Grant with PKCE: https://laravel.com/docs/9.x/passport#code-grant-pkce
I have an application for the localhost that downloads data from google sheet - it works as it should ...
I moved it to an external server on mikrohost.pl - I made a new user, set it up like the previous account ...
and trying to log in crashes me:
The authorization state [state=HA-SE26O0CYMZJF73RILN8P4H1TQAD9KVU5WBGX] of this page is either invalid or has already been consumed.
my config.php file:
<?php
require_once '../vendor/autoload.php';
require_once 'class-db.php';
define('GOOGLE_CLIENT_ID', 'GOOGLE_CLIENT_ID.apps.googleusercontent.com');
define('GOOGLE_CLIENT_SECRET', 'wygenerowanyWconsoliGoogle');
$config = [
'callback' => 'http://adres/do/callback.php',
'keys' => [
'id' => GOOGLE_CLIENT_ID,
'secret' => GOOGLE_CLIENT_SECRET
],
'scope' => 'https://www.googleapis.com/auth/spreadsheets',
'authorize_url_parameters' => [
'approval_prompt' => 'force', // to pass only when you need to acquire a new refresh token.
'access_type' => 'offline'
]
];
$adapter = new Hybridauth\Provider\Google( $config );
?>
My callback.php
<?php
require_once 'config.php';
try {
$adapter->authenticate();
$token = $adapter->getAccessToken();
$db = new DB();
if($db->is_table_empty()) {
$db->update_access_token(json_encode($token));
echo "Access token inserted successfully.";
}
}
catch( Exception $e ){
echo $e->getMessage() ;
}
I generated a new OAuth dataset for the google user, where I am logged in to localhost app, but I still get the same error - the application works on localhost. I don't know what's wrong
I have PHP v7.4.21
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.
I want to configure my Symfony4 application to read and send e-mails using the msgraph-sdk-php library.
My app would be reading and sending e-mail from a single account, whose password I don't want to expose to my app's users. Thus, I wouldn't be using OAuth for login.
My first experience was this piece of code (to retrieve mailbox user profile):
<?php
namespace App\Graph;
use Microsoft\Graph\Exception\GraphException;
use Microsoft\Graph\Graph;
use Microsoft\Graph\Model\User;
class GraphService
{
function sentTestMessage() {
$userId = "************************************";
$tenantId = "************************************";
$clientId = "************************************";
$clientSecret = "***************************";
$guzzle = new \GuzzleHttp\Client();
$url = 'https://login.microsoftonline.com/' . $tenantId . '/oauth2/token?api-version=1.0';
$token = json_decode($guzzle->post($url, [
'form_params' => [
'client_id' => $clientId,
'client_secret' => $clientSecret,
'resource' => 'https://graph.microsoft.com/',
'grant_type' => 'client_credentials',
],
])->getBody()->getContents());
$accessToken = $token->access_token;
$graph = new Graph();
$graph->setAccessToken($accessToken);
$user=new \stdClass();
try {
$user = $graph->createRequest("GET", "/users/".$userId)
->setReturnType(User::class)
->execute();
} catch (GraphException $e) {
$user->getGivenName=$e->getMessage();
}
return "Hello, I am $user->getGivenName() ";
}
}
But then Symfony shows me an exception page with this message:
Client error: GET https://graph.microsoft.com/v1.0/users/... resulted in a 403 Forbidden response:
{
"error": {
"code": "Authorization_RequestDenied",
"message": "Insufficient privileges to complete the ope (truncated...)
Now the same query works when run in https://developer.microsoft.com/en-us/graph/graph-explorer with the same user logged in.
These are the permissions I gave the app:
What should I do to overcome the problem above described?
You used client credentials flow to get access token in your code, so you need application permission instead of delegated permission.
I'm trying to access Firebase from a server using PHP, the Google Auth library, and a wrapper for Firebase's REST...This works great to accomplish that:
use Firebase\JWT\JWT;
use Google\Auth\Credentials\ServiceAccountCredentials;
use Google\Auth\HttpHandler\HttpHandlerFactory;
use GuzzleHttp\Client;
$email = 'account#email.com';
$key = 'private_key_goes_here';
$scopes = [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/firebase.database',
];
$creds = [
'client_email' => $email,
'private_key' => $key,
];
$serviceAccount = new ServiceAccountCredentials($scopes, $creds);
$handler = HttpHandlerFactory::build(new Client());
$token = $serviceAccount->fetchAuthToken($handler);
$firebase = new \Firebase\FirebaseLib($url, $token);
$value = $firebase->get('test/hello');
# $value now stores "world"
However, this requires the security rules in Firebase to be universal read / write, which I do not want. If I update my security rules to this:
{
"rules": {
"test": {
".read": "auth != null"
}
}
}
The result in $value becomes {"error": "Permission denied"}. I've searched extensively, and tried numerous permutations and possible solutions, with no conclusive results.
I've used this code to provide JWT tokens to end clients, which can successfully use them and leverage the security rules with no problem. I initially tried that same approach for the server, but was unsuccessful. I opted to try to combine the two methods:
# Snipping code that didn't change...
$serviceAccount = new ServiceAccountCredentials($scopes, $creds);
$handler = HttpHandlerFactory::build(new Client());
$payload = [
'iss' => $email,
'sub' => $email,
'aud' => 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit',
'iat' => time(),
'exp' => time() + 60 * 60,
'uid' => '123',
'claims' => [
'uid' => '123',
],
];
$payload = $serviceAccount->updateMetadata($payload);
$token = JWT::encode($payload, $key, 'RS256');
$firebase = new \Firebase\FirebaseLib($url, $token);
$value = $firebase->get('test/hello');
This seems to get close, but $value now contains {"error": "Missing claim 'kid' in auth header."}. To resolve this, I modified the encode call:
$token = JWT::encode($payload, $key, 'RS256', 'key_id_goes_here');
Which results in a slightly different error: Invalid claim 'kid' in auth header., suggesting I'm on the right track...But not quite there. Using the JWT token directly yields the exact same results. Any ideas what I'm doing wrong? The email, private key, and key id all came directly from the json credential file provided when I created the service account.
I've looked at dozens of pages of documentation and posts, here are the ones that were the most helpful:
Using JWT for Server Auth (Firebase Docs)
Using Custom Tokens to make REST requests to FB DB as an admin
Is it still possible to do server side verification of tokens in Firebase 3?
Cross posted to the Firebase Google Group.
You can specify an auth_variable_override query parameter when authenticating with a service account that will become the auth variable in the security rules. It should be a properly escaped JSON object. For example to do {"uid":123} you'd want to add:
?auth_variable_override=%7B%22uid%22%3A%22123%22%7D
to the end of your request URL.
Ultimately, the solution I ended up using was to switch PHP libraries. I initially dismissed this library because it is moving toward PHP7 only support, which I'm not ready to migrate to yet, but the current version (1.1) worked fine:
use Kreait\Firebase\Configuration;
use Kreait\Firebase\Firebase;
$clientId = '1234567890';
$email = 'account#email.com';
$key = 'private_key_goes_here';
$url = 'https://example.firebaseio.com';
$fbConfig = new Configuration();
$fbConfig->setAuthConfigFile([
'type' => 'service_account',
'client_id' => $clientId,
'client_email' => $email,
'private_key' => $key,
]);
$fb = new Firebase($url, $fbConfig);
$value = $fb->get('test/hello');
# $value now stores "world"