Authentication failure trying to upload Vimeo video - php

Using the official Vimeo PHP Library (https://github.com/vimeo/vimeo.php)
Attempting to upload a video using the upload() method of the client library return the following http response:
The authentication token is missing a user ID and must be provided when uploading a video.
However the Client Id, Client Secret, and Token are all being set on the client before calling the upload() method:
Client initialization:
$this->setClient(new Vimeo($this->clientId, $this->clientSecret, $this->token));
Call to upload method:
try{
$videoUri = $this->getClient()->upload($path, [
'name' => $name,
'privacy' => [
'view' => 'anybody'
]
]);
return $videoUri;
} catch (\Exception $e) {
dump($e);
return false;
}
There is a tutorial endpoint, which I called using the client created above and got the following response:
{
"message": "Success! You just interacted with the Vimeo API. Your dev environment is configured correctly, and the client ID, client secret, and access token that you provided are all working fine.",
"next_steps_link": "https://developer.vimeo.com/api/guides/videos/upload",
"token_is_authenticated": false
}
Any suggestions welcomed!

The token you are generating is unauthenticated, meaning it can only read metadata public on vimeo.com -- it cannot be used to upload, retrieve data, or perform other actions on an account.
The giveaway is in the last line of the /tutorials response:
"token_is_authenticated": false
Take a look at the authentication docs on the Vimeo Developer Site here: https://developer.vimeo.com/api/authentication#understanding-auth-workflows

The issue was the the token being used was an unauthenticated token. I'd made the assumption that you only needed an authenticated token to upload on behalf of another user. By generating a new authenticated token that had the 'upload' scope I was able to upload the video using the exact code posted above.

Related

How can I re-acquire a Shopify OAuth access token for a store that has previously installed my application?

I requested authorization for a public application to be able to access store data via the Shopify API.
The store successfully authorized my application via an authorization request URL such as
https://some-store.myshopify.com/admin/oauth/authorize?client_id=123abc&scope=read_inventory%2Cread_products&redirect_uri=http%3A%2F%mysite.com%2Fauth.php&state=123456
and the response was passed back to my application. This response (containing the code that can be exchanged for a permanent access token) was mishandled by my application (an error on the page meant that the access token was not stored).
Everything I read regarding requesting these tokens involves authorization by the store - but given the store has already authorized my application, passed back the code and that code has already successfully been exchanged for a token: is there a way my application can request that same token or a fresh one using my API keys given that the application is already authorized?
The only method I currently can find for requesting a token requires starting back at the beginning and fetching a code for exchange etc.
I working in PHP and using Luke Towers' php shopify wrapper
This stage was completed successfully:
function check_authorization_attempt()
{
$data = $_GET;
$api = new Shopify($data['shop'], [
'api_key' => '123',
'secret' => '456',
]);
$storedAttempt = null;
$attempts = json_decode(file_get_contents('authattempts.json'));
foreach ($attempts as $attempt) {
if ($attempt->shop === $data['shop']) {
$storedAttempt = $attempt;
break;
}
}
return $api->authorizeApplication($storedAttempt->nonce, $data);
}
$response = check_authorization_attempt();
and I would have been able to read the access token from :
$access_token = $response->access_token;
But this was the stage at which my application hit an error in accessing a database in which to write said token.
I cannot repeat it without repeating the auth request because the data in $_GET that's passed to this function comes from Shopify's response to the shop owner authorizing the access, and includes amoung other things the code for exchange.
You have to re-ask for authorization. It is no one's fault but yours that your persistence layer code was incorrect. So there is nothing you can do to change that. Ensure your code works. Since the client has no token in your App persistence layer, your App will retry the authorization token exchange. They do not have to delete your App first. So basically, the next time your client tries to use the App, YES they will asked to approve it, but who cares, they will, and you'll get a good auth token to store. You have fixed your code (right), so that will work. You are one step closer to glory.
Shopify does return the Permanent Access Token, but the ACCESS_MODE must be "Offline" for the token to be permanent.
With ACCESS_MODE offline, your app receives the permanent access token
to make requests whenever you want, without the user's permission.
Documentation:
https://shopify.dev/tutorials/authenticate-with-oauth#step-2-ask-for-permission
https://shopify.dev/concepts/about-apis/authentication#api-access-modes

How can I get this Google Login ID Token from this Android app to verify server-side?

I'm getting the ID Token in my Android app by initiating like this:
GoogleSignInOptions googleSignInOptions = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.server_client_id))
.requestEmail()
.build();
Where server_client_id is my SERVER's Oauth Client ID. Then later I request the token with googleAccount.getIdToken()
Then on my server (PHP), when I verify the token I verify it like this:
$client = new \Google_Client(['client_id' => getenv("GOOGLE_CLIENT_ID")]);
try {
$payload = $client->verifyIdToken($this->idToken);
} catch (\Exception $e){
throw new BadRequestHttpException($e->getMessage());
}
if($payload){
$this->verifyPayload($payload);
} else {
throw new AccessDeniedHttpException("Invalid ID Token");
}
Where GOOGLE_CLIENT_ID is my ANDROID's Oauth Client ID
I'm following this guide: https://developers.google.com/identity/sign-in/android/start-integrating.
On this page it says: https://developers.google.com/identity/sign-in/android/backend-auth
When you configure Google Sign-in, call the requestIdToken method and pass it your server's web client ID.
Hence why I'm using the server_client_id in my Android app. Is this correct?
// Specify the CLIENT_ID of the app that accesses the backend
And this is why I'm using the ANDROID client ID from my server.
Is this right? To be using each other's Oauth client_id's? Or should they both be using the Android CLIENT_ID?
Thanks in advance
OK I figured it out. Both the Android app and Webserver need to use the SERVER's key. Even though I created a client ID for the app using it's key SHA1 and everything.
Send authorisation request from the app, using client_id and public key
As a response you get a temporary token
Send the token to your API. Use it to make another request (from the API to Google), to get a long lived token
Store the long-lived token. Use it to request data from google, f.e. user information
Important: never use server key inside the app

Google Client fails to verify token on back-end server

I have downloaded project from Try Sign-in for Android, created project on Google API Console. I have successfully configured signin project and run the application in the emulator. I pasted OAuth Web Client ID into strings.xml. Then opened this IdTokenActivity and got idToken.
I copied this token and created request using POSTMAN to my server http://localhost/api/googlesignin/hello
On server I have this code:
public function hello(Request $request)
{
$idToken = $request->get('idToken');
$client = new \Google_Client([
'client_id' => '[SAME CLIENT ID AS THE ONE IN STRINGS.XML]'
]);
$payload = $client->verifyIdToken($idToken);
var_dump($payload)
}
But this returns FALSE meaning it fails to verify $idToken.
But using endpoint token verification like this:
https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={$idToken}
returns status code 200 and all information about this user.
I have no idea why GoogleClient fails to verify token? Am I using wrong Client ID?
EDIT:
I have found the source of the problem: I have accidentally pasted twice ClientID:
'client_id' => 'client-id-tokenclient-id-token'
-.-

Uploading a YouTube video via V3 Data API -- Does the user have a channel?

Uploading videos to a YouTube account without a channel fails.
I'm using the YouTube Data API (Version 3) and the Google APIs Client Library for PHP to upload custom videos to a users channel. The user has given authorization (via OAuth2) to manage their YouTube account, but it's possible that the user has never created a YouTube channel.
The question is: How do I check if the user has a valid channel before trying to start an upload? Ideally, I'd want to check right after authentication.
After a valid oAuth token is received, you can make the following call (this assumes that '$youtube' represents your Google_YoutubeService object):
$channelsResponse = $youtube->channels->listChannels('contentDetails', array(
'mine' => 'true',
));
If $channelsResponse['items'] is empty, then the authenticated user has no channels.
http get calls for 'https://www.googleapis.com/youtube/v3/channels?part=contentDetails&mine=true&key={}&access_token={}'
will return items even if the user does not has channels.
an example response is:
{u'etag': u'"IHLB7Mi__JPvvG2zLQWweA"',
u'items': [{u'contentDetails': {u'googlePlusUserId': u'107123',
u'relatedPlaylists': {u'favorites': u'FLMZmB78WMw',
u'likes': u'LLMZmB78we',
u'uploads': u'UUMZmB78Wwef',
u'watchHistory': u'HLMZmB78Wwe',
u'watchLater': u'WLMZmB78WMDgwef'}},
u'etag': u'"IHLB7Mi__JPvvG2zLQWAg8l36"',
u'id': u'UCMZmB78WMD',
u'kind': u'youtube#channel'}],
u'kind': u'youtube#channelListResponse',
u'pageInfo': {u'resultsPerPage': 1, u'totalResults': 1}}
in case that 'googlePlusUserId' is in response, then the user has channel.

Browser uploads to s3 with instance roles

This is a follow-up on my previous question regarding policy document signing using instance profiles.
I'm developing a system that allows drag & drop uploads directly to an S3 bucket; an AJAX request is first made to my server containing the file metadata. Once verified, my server responds with the form parameters that are used to complete the upload.
The process of setting up browser based uploads is well explained here and it all works as expected in my local test environment.
However, once my application gets deployed on an EC2 instance, I'm seeing this error when the browser attempts to upload the file:
<Error>
<Code>InvalidAccessKeyId</Code>
<Message>The AWS Access Key Id you provided does not exist in our records.</Message>
<RequestId>...</RequestId>
<HostId>...</HostId>
<AWSAccessKeyId>ASIAxxxyyyzzz</AWSAccessKeyId>
</Error>
The value of ASIAxxxyyyzzz here comes from the instance role credentials, as obtained from the metadata service; it seems that those credentials can't be used outside of EC2 to facilitate browser based uploads.
I've looked at the Security Token Service as well to generate another set of temporary credentials by doing this:
$token = $sts->assumeRole(array(
'RoleArn' => 'arn:aws:iam::xyz:role/mydomain.com',
'RoleSessionName' => 'uploader',
));
$credentials = new Credentials($token['Credentials']['AccessKeyId'], $token['Credentials']['SecretAccessKey']);
The call givens me a new set of credentials, but it give the same error as above when I use it.
I hope that someone has done this before and can tell me what stupid thing I've missed out :)
The AWS docs are very confusing on this, but I suspect that you need to include the x-amz-security-token parameter in the S3 upload POST request and that its value matches the SessionToken you get from STS ($token['Credentials']['SessionToken']).
STS temporary credentials are only valid when you include the corresponding security token.
The AWS documentation for the POST request states that:
Each request that uses Amazon DevPay requires two x-amz-security-token
form fields: one for the product token and one for the user token.
but that parameter is also used outside of DevPay, to pass the STS token and you would only need to pass it once in the form fields.
As pointed out by dcro's answer, the session token needs to be passed to the service you're using when you use temporary credentials. The official documentation mentions the x-amz-security-token field, but seems to suggest it's only used for DevPay; this is probably because DevPay uses the same type of temporary credentials and therefore requires the session security token.
2013-10-16: Amazon has updated their documentation to make this more obvious.
As it turned out, it's not even required to use STS at all; the credentials received by the metadata service come with such a session token as well. This token is automatically passed for you when the SDK is used together with temporary credentials, but in this case the final request is made by the browser and thus needs to be passed explicitly.
The below is my working code:
$credentials = Credentials::factory();
$signer = new S3Signature();
$policy = new AwsUploadPolicy(new DateTime('+1 hour', new DateTimeZone('UTC')));
$policy->setBucket('upload.mydomain.com');
$policy->setACL($policy::ACL_PUBLIC_READ);
$policy->setKey('uploads/test.jpg');
$policy->setContentType('image/jpeg');
$policy->setContentLength(5034);
$fields = array(
'AWSAccessKeyId' => $credentials->getAccessKeyId(),
'key' => $path,
'Content-Type' => $type,
'acl' => $policy::ACL_PUBLIC_READ,
'policy' => $policy,
);
if ($credentials->getSecurityToken()) {
// pass security token
$fields['x-amz-security-token'] = $credentials->getSecurityToken();
$policy->setSecurityToken($credentials->getSecurityToken());
}
$fields['signature'] = $signer->signString($policy, $credentials);
I'm using a helper class to build the policy, called AwsUploadPolicy; at the time of writing it's not complete, but it may help others with a similar problem.
Permissions were the last problem; my code sets the ACL to public-read and doing so requires the additional s3:PutObjectAcl permission.
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:PutObject",
"s3:PutObjectAcl"
],
"Sid": "Stmt1379546195000",
"Resource": [
"arn:aws:s3:::upload.mydomain.com/uploads/*"
],
"Effect": "Allow"
}
]
}

Categories