Twinfield API OAuth2.0 getaccessToken php-twinfield/twinfield - php

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.

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

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;
}

How to use Laravel Passport with Password Grant Tokens?

I just read the https://laravel.com/docs/5.6/passport documentation and I have some doubts that hopefully someone could help me with:
First, some context, I want to use Passport as a way to provide Oauth authentication for my mobile app (first-party app).
When I use php artisan passport:client --password I get back a Client ID and a Client Secret. Does this value have to be fixed on my app? for example storing them hardcoded or as a "settings" file? If the values shouldn't be stored then how should it work?
To register a user to my app I use: $user->createToken('The-App')->accessToken; I get that the accessToken will be the one used for sending on all my requests as a Header (Authorization => Bearer $accessToken) but what exactly is "The-App" value for?
For login the user I'm using the URL: http://example.com/oauth/token and sending as parameters:
{
"username": "user#email.com",
"password": "userpassword",
"grant_type": "password",
"client_id": 1, // The Client ID that I got from the command (question 1)
"client_secret": "Shhh" // The Client Secret that I got from the command (question 1)
}
When I login the user using the previous endpoint I get back a refresh_token, I read that I could refresh the token through http://example.com/oauth/token/refresh but I try to request the refresh I got Error 419, I removed the url oauth/token/refresh from the csrf verification and now I get back "message": "Unauthenticated.", I'm making the following request:
Content-Type: x-www-form-urlencoded
grant_type: refresh_token
refresh_token: the-refresh-token // The Refresh Token that I got from the command (question 3)
client_id: 1 // The Client ID that I got from the command (question 1)
client_secret: Shhh // The Client Secret that I got from the command (question 1)
scope: ''
Should I use this endpoint? or is not necessary given the app I'm trying to develop.
Finally, there are a lot of endpoints that I get from passport that I don't think I will use for example: oauth/clients*, oauth/personal-access-tokens* is there a way to remove them from the endpoints published by passport?
Thanks a lot for your help!
If you are consuming your own api then you don't need to call http://example.com/oauth/token
for user login because then you need to store client_id and client_secret at app side. Better you create an api for login and there you can check the credentials and generate the personal token.
public function login(Request $request)
{
$credentials = $request->only('email', 'password');
if (Auth::attempt($credentials)) {
// Authentication passed...
$user = Auth::user();
$token = $user->createToken('Token Name')->accessToken;
return response()->json($token);
}
}
Finally, there are a lot of endpoints that I get from passport that I
don't think I will use for example: oauth/clients*,
oauth/personal-access-tokens* is there a way to remove them from the
endpoints published by passport?
You need to remove Passport::routes(); from AuthServiceProvider and manually put only required passport routes. I think you only need oauth/token route.
what exactly is "The-App" value for?
if you check oauth_access_tokens table it has name field. $user->createToken('Token Name')->accessToken; here the "Token Name" stored in name field.
How to use Laravel Passport with Password Grant Tokens?
To generate password grant token you have to store client_id and client_secret at app side (not recommended, check this ) and suppose if you have to reset the client_secret then the old version app stop working, these are the problems. To generate password grant token you have to call this api like you mention in step 3.
$http = new GuzzleHttp\Client;
$response = $http->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'username' => 'taylor#laravel.com',
'password' => 'my-password',
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
Generate token from refresh_token
$http = new GuzzleHttp\Client;
$response = $http->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'refresh_token',
'refresh_token' => 'the-refresh-token',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
You can look this https://laravel.com/docs/5.6/passport#implicit-grant-tokens too.
Tackling Question 5
Finally, there are a lot of endpoints that I get from passport that I don't think I will use for example: oauth/clients*, oauth/personal-access-tokens* is there a way to remove them from the endpoints published by passport?
Passport::routes($callback = null, array $options = []) takes an optional $callback function and optional $options argument.
The callback function takes a $router argument from which you can then choose which routes to install as shown below in your AuthServiceProvider.php that is enabling a more granular configuration:
Passport::routes(function ($router) {
$router->forAccessTokens();
$router->forPersonalAccessTokens();
$router->forTransientTokens();
});
Passport::tokensExpireIn(Carbon::now()->addMinutes(10));
Passport::refreshTokensExpireIn(Carbon::now()->addDays(10));
This way we only create the passport routes that we need.
forAccessTokens(); enable us to create access tokens.
forPersonalAccessTokens(); enable us to create personal tokens although we will not use this in this article. Lastly,
forTransientTokens(); creates the route for refreshing tokens.
If you run php artisan route:list you can see the new endpoints installed by Laravel Passport.
| POST | oauth/token | \Laravel\Passport\Http\Controllers\AccessTokenController#issueToken
| POST | oauth/token/refresh | \Laravel\Passport\Http\Controllers\TransientTokenController#refresh

How to limit user actions with Laravel Passport Scopes + Password Grant Type

I have set up the Laravel Passport package for Laravel 5.3 just as described in the official documentation (https://laravel.com/docs/5.3/passport#introduction).
I want the API to be consumed by a mobile application, so I am trying to implement Password Grant Tokens. I have created a password grant client, and the token request process...
$response = $http->post('http://my-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'username' => 'my#email.com',
'password' => 'my-password',
'scope' => '',
],
]);
...Just works as expected, returning an access-token and a refresh-token for one of my users.
But now I want to define some scopes so I can limit the access of users... Following the documentation again, I have them defined in boot method of AuthServiceProvider.php like:
Passport::tokensCan([
'admin' => 'Perform every action',
'user' => 'Perform only normal user actions',
]);
In this scenario, if a "malicious" normal user requested a token (using the above POST call) specifying 'scope' => 'admin', he or she would get an 'admin' token... and that is not what I want.
Thus, I would like to know how is the workflow in this situation to effectively limit the access to normal users, and where do I have to implement the scope validation logic.
Thanks in advance.
One way to go about this would be to create a middleware
For example if you only want users with an email from example.com to request the admin domain you can do something like this
Example ScopeLogic.php middleware:
if ($request->input('grant_type') === 'password') {
$scope = $request->input('scope');
$username = $request->input('username');
if ($scope === 'admin' && (strpos($username, '#example.com') === false)) {
return response()->json(['message' => "Not authorized to request admin scope"], 401);
}
}
return $next($request);
Of course, you would have to add this scope to your $routeMiddleware array in Kernel.php
protected $routeMiddleware = [
...
'check-scopes' => \App\Http\Middleware\ScopeLogic::class
]
As well as wrap Passport::routes() in AuthServiceProvider.php to check for this middleware
\Route::group(['middleware' => 'check-scopes'], function() {
Passport::routes();
});
Passport will also check that a correct username and passport combination was passed so you don't have to worry about that in the middleware
In my opinion, I think what confuses most people with OAuth and APIs is that scopes are tied to "clients" and not the "resource owner" themselves. Clients should be able to talk to an API using an admin scope or no scopes at all if needed. If they use an admin-ish type scope together with user context (password grant, authorization code grant, etc), then there is no stopping them from making calls that require such a scope against that user in the API. To me, the only person that can truly be classified as malicious would be one who manages to steal an access token containing an admin scope. That is why API implementors are allowed to specify what scopes to grant a client and if it's a first party app that uses something like the Password Grant, then you as a user has no choice but to trust it with your data.
I don't know how one would do this and use the retrieved token inside another's mobile app but if you did try requesting a token manually yourself with an admin scope, then I really don't see anything wrong that (other than you giving the app more control with you set as user context, so it may even be counter productive?)
If you need more control than that, then you need to go past your API and create something like application-level permissions for each user inside your resource server.
I forget where I read it, some Github issues somewhere, but apparently Laravel doesn't have that ability built in, each client is the treated the same equally, out of the box.
A user provided a good solution, and I built upon it here: https://stackoverflow.com/a/55285483/1132557

Getting Google Refresh Token with Laravel Socialite

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();

Categories