Getting Google Refresh Token with Laravel Socialite - php

Using Laravel Socailite to connect to the Google API and I am getting back a connection fine, however this access doesn't return a refresh token so my connection is timing out.
$scopes = [
'https://www.googleapis.com/auth/webmasters',
'https://www.googleapis.com/auth/webmasters.readonly',
'https://www.googleapis.com/auth/analytics.readonly',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email',
];
$parameters = ['access_type' => 'offline'];
return Socialite::driver('google')->scopes($scopes)->with($parameters)->redirect();
How do I get the refresh token back?

When you redirect your users to Google, set access_type to offline with the with() method when redirecting, like this:
return Socialite::driver('google')
->scopes() // For any extra scopes you need, see https://developers.google.com/identity/protocols/googlescopes for a full list; alternatively use constants shipped with Google's PHP Client Library
->with(["access_type" => "offline", "prompt" => "consent select_account"])
->redirect();

Related

Google Cloud Platform API Creating Service Accounts by a Service account (PHP)

I'm trying to create a service account on a project using a service account but I can't get it to work. Right now I'm just simply trying to list all service accounts from a project using a service account who was permission but it returns a 401 error. Is this even possible to do with a service account?
$sa = new ServiceAccountCredentials([
'iam.serviceAccounts.list'
], base_path() . '/credentials.json');
$middleware = new AuthTokenMiddleware($sa);
$stack = HandlerStack::create();
$stack->push($middleware);
// create the HTTP client
$client = new Client([
'handler' => $stack,
'base_uri' => 'https://iam.googleapis.com',
'auth' => 'google_auth' // authorize all requests
]);
$response = $client->get('v1/projects/project-id-1/serviceAccounts');
var_dump((String) $response->getBody());
Response:
{
"error": {
"code": 401,
"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
}
}
Edit:
My packages:
"google/auth": "^1.19",
"guzzlehttp/guzzle": "^7.0.1"
The second line of your code 'iam.serviceAccounts.list' is incorrect. You are specifying an IAM permission. The method requires a scope.
Refer to the documentation for this method.
$sa = new ServiceAccountCredentials([
'https://www.googleapis.com/auth/cloud-platform'
], base_path() . '/credentials.json');
With your second parameters, when the method is called, a lookup of the requested scope is performed. Since you did not specify a valid scope, the end result is you have a credential with no permissions. That causes the error message:
Request had invalid authentication credentials. Expected OAuth 2 access token ...
Do not control permissions via scopes when using service accounts. Use IAM roles to define which permissions a service account has. Then use the cloud-platorm scope.
I wrote an article on Google Cloud credentials and PHP that might help with details:
Google Cloud Application Default Credentials – PHP

Twinfield API OAuth2.0 getaccessToken php-twinfield/twinfield

I am currently trying to setup the Twinfield API, it should be pretty straight forward when using the php-twinfield/twinfield library. But there is one thing I don't fully understand.
Here is my code:
$provider = new OAuthProvider([
'clientId' => 'someClientId',
'clientSecret' => 'someClientSecret',
'redirectUri' => 'https://example.org/'
]);
$accessToken = $provider->getAccessToken("authorization_code", ["code" => ...]);
$refreshToken = $accessToken->getRefreshToken();
$office = \PhpTwinfield\Office::fromCode("someOfficeCode");
$connection = new \PhpTwinfield\Secure\OpenIdConnectAuthentication($provider,
$refreshToken, $office);
The $accessToken require something on the dots, some sort of code. I am not sure what that should be...
I hope someone can help me out. Thanks already!
I am still stuck with oauth2 setup... the provider seems to have all the information it needs to have. It returns a code which is needed to retrieve an accessToken. But, trying to get one using the following code:
$accessToken = $provider->getAccessToken('authorization_code',
['code' => $_GET['code']]);
This will return 'invalid_grant'.
I have tried to reset my clientSecret... but that did not help.
I hope somebody can help me any further.
To access the Twinfield API the users must be authenticated. You can either do this by specifying a username and password or using OAuth2. When using OAuth2 you delegate the authentication to a so called OAuth Provider. After the user authenticated, the provider will redirect the user's browser to an endpoint (redirectUri) at your application. That request, that your application receives, has a GET parameter called code. Your app will then exchange the code for a token using its clientId and clientSecret and HTTP POST. Which means that your application must be registered at the OAuth2 provider so that the provider (e.g. github, facebook, google, ...) can validate the client credentials and return a token. And you will have to configure your provider variable to point to the OAuth provider that you connect with.
$provider = new OAuthProvider([
'clientId' => 'XXXXXX', // The client ID assigned to you by the provider
'clientSecret' => 'XXXXXX', // The client password assigned to you by the provider
'redirectUri' => 'https://example.com/your-redirect-url/',
'urlAuthorize' => 'https://login.provider.com/authorize', //where the user's browser should be redirected to for triggering the authentication
'urlAccessToken' => 'https://login.provider.com/token', //where to exchange the code for a token
'urlResourceOwnerDetails' => 'https://login.provider.com/resource' //where to get more details about a user
]);
// If we don't have an authorization code then get one
if (!isset($_GET['code'])) {
// Fetch the authorization URL from the provider
// Redirect the user to the authorization URL.
}
Twinfield makes use of league/oauth2-client library for implementing OAuth. Therefore, refer to https://oauth2-client.thephpleague.com/usage/ for the details on how to setup an OAuth client in the twinfield library. league/oauth2-client supports some providers out of the box and allows third-party providers. Your provider may be in any of the lists. If not, refer to the documentation of your provider to get the right URLs.

KnpUOAuth2ClientBundle and Google PHP API

I have a Symfony 5 Application that is authenticating users by their Google account using KnpUOAuth2ClientBundle.
I now want to integrate the Google Drive API so the user can upload files to his Drive.
https://developers.google.com/drive/api/v3/quickstart/php
is giving some documentation about that.
What I am wondering: If the user is already authenticated (I have a valid user access token), do I have to run through the whole authentication process again or can I use the authentication token that was generated by KnpUOAuth2ClientBundle? And how do I get this authentication token as an object? When I am trying to get it via $client->getAccessToken() I get the error "Invalid State".
The answer is: Yes, you can use the existing login token to authenticate with further services like Google Drive API. You just need the existing login token as a string.
Be aware that you might have to add additional scopes to the login like this:
$clientRegistry
->getClient('google_main') // key used in config/packages/knpu_oauth2_client.yaml
->redirect([
'email',
'profile', // the scopes you want to access
Google_Service_Drive::DRIVE_FILE,
], []);
Then in your Google Drive Service you can initiate the Client like this:
/**
* Returns an authorized API client.
* #return Google_Client the authorized client object
*/
protected function initClient()
{
$user = $this->security->getUser();
$googleAccessToken = $user->getGoogleAccessToken();
$this->client->setApplicationName('MY APPLICATION');
$this->client->setScopes(Google_Service_Drive::DRIVE_METADATA_READONLY);
$this->client->setAccessType('offline');
$this->client->setPrompt('select_account consent');
$googleToken = [
'access_token' => $googleAccessToken,
'id_token' => $googleAccessToken,
// user is having a valid token from the login already
'created' => time() - 3600,
'expires_in' => 3600,
];
$this->client->setAccessToken($googleToken);
return $this->client;
}

PHP Slim with Firebase JWT

I am trying to integrate Firebase Auth with PHP Slim (JWT) without any luck. I login using my firebase user and save my token correctly. Then I set my midleware.php like this:
$app->add(new Tuupola\Middleware\JwtAuthentication([
"ignore" => ["/countries","/faqs"],
"secret" => $secrets,
"secure" => false
]));
where $secrets is the kid coming from securetoken#system.gserviceaccount.com. However I keep getting an error 401 not authorized.
Same code works when I try it with a custom $secret and custom jwt. Does Firebase need something extra in the JwtAuthentication?
So the issue was how JwtAuthentication from Tuupola handles the kid (it is actually kid - key id which comes from googles public keys).
In vanilla PHP, I had to get the secrets from google, then split it into arrays before using it. However, this is all done by Tuupola internally, which caused my issue.
The correct Middleware for Slim 4 to work with Firebase Auth is the following:
return function (App $app) {
$app->add(CorsMiddleware::class);
$container = $app->getContainer();
$rawPublicKeys = file_get_contents('https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com');
$keys = json_decode($rawPublicKeys, true);
$app->add(new Tuupola\Middleware\JwtAuthentication([
"ignore" => ["/api/records/countries","/faqs","/","/answer"],
"algorithm" => ["RS256"],
"header" => "X-Authorization",
"regexp" => "/Bearer\s+(.*)$/i",
"secret" => $keys,
"secure" => false
}
]));
};

Can't get Auth object and cookies by consuming my own Laravel API

I'm currently trying to build a secure SPA application in Laravel by using :
Laravel 5.6
Laravel Passport
Guzzle client
To make the whole application secure, I created a proxy to prefix all requests to the API and :
User the password grand type of token
Hide the client ID
Hide the client secret
Add automatic scopes based on the role of the user
This is how the Proxy works :
// The proxify endpoint for all API requests
Route::group(['middleware' => ['web']], function ()
{
Route::any('proxify/{url?}', function(Request $request, $url) {
return Proxify::makeRequest($request->method(), $request->all(), $url);
})->where('url', '(.*)');
});
Each time a request is made, it goes through that package I built to create the access token, refreshing it, or deleting it.
To create the access token for the user I'm using a MiddleWare at loggin :
$response = $http->post('http://myproject.local/proxify/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'username' => $request->get('email'),
'password' => $request->get('password'),
]
]);
This is working well, excepting the fact that I'm setting cookies in the Proxify::makeRequest, so I have to create them in the call, return them in the $response, and then at the end of the Middleware, attaching them to the request (Cookie::queue and Cookie::Make are not working in a Guzzle call it seems).
The access token is created and stored in a cookie.
First problem is that in this call, even in the middleware, and especially in that URL http://myproject.local/proxify/oauth/token, I don't have any access to the Auth trait, even if it's specified as a middleware attached to the route, so impossible to fetch information from the authenticated user.
Then the other problem is that when I'm making a call to get a ressource API such as :
$http = new Client();
$response = $http->get('http://myproject.local/proxify/api/continents');
$continents = $response->getBody()->getContents();
return view('dashboard')->with("continents", $continents);
In that case, when I'm calling the URL, the proxy is not able to get the access_token defined in the cookie with the CookieFacade through the HTTP call, neither the Auth object I'm whiling to use. The $_COOKIE variable is not working neither.
What is wrong with my structure so that I don't have any access to the cookie even if it's set and in the browser ? Any other way to get the info ? I tried to get the cookie from the request in the proxy, not working.
Have you tried using the Illuminate or Symfony Request classes and handling the routing via the Laravel instance? My immediate suspicion is Guzzle is the culprit behind no cookies coming through with the requests. Cookie::queue() is a Laravel specific feature so I wouldn't think Guzzle would know anything about them.
Replace Guzzle in one of the routes where the issue occurs. Start with a new Request instance and make the internal api call like:
// create new Illuminate request
$request = Request::create('/api/users', $action, $data, [], [], [
'Accept' => 'application/json',
]);
// let the application instance handle the request
$response = app()->handle($request);
// return the Illuminate response with cookies
return $response->withCookies($myCookies);
I do something similar to this in a couple applications and never had any problems with cookies or accessing server variables. Granted, it's only for authentication, the rest of the api calls are through axios.

Categories