Javascript request to cross orgin PHP application - javascript

I have a PHP application that I want to run through XMLHttpRequests on my front end part, this works fine as it's pratically the same as "normal". This works fine.
I get really confused when I start trying to do secure cross origin requests. For instance, some requests I want to only permit if a user is logged in. But, when testing if a session is still there, it doesn't exist anymore. I just don't want random people tapping into someones profile, for instance. Or gain any sort of data I don't want random people to see.
What I want
Requests through this model shown below, but make them secure. Because some data I want for protect to logged-in users only. How do you do that? I can't wrap my mind around that.
Basically, I now have the issue that I can't check on the PHP end if a user has a active session, as PHP sees it as a totally new thing. Essentially, how do you do it like web broswers do it? It's probably just really stupid, but I can't wrap my mind around it.
What I've tried
I've tried requesting where one sets the $_SESSION, then requesting it where it returns $_SESSION, but it return nothing. This means I can't check if the request comes from a loggedin user.

You can use JSON Web Tokens for communicating securely across devices.
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA.
Source
You basically send a token with each request to sources you want to secure behind a login. Then server side you veryfy that token. Upon login you generate that token and include it in your response. In your frontend you can store the token in local storage on the client machine. For example jQuery has this plugin for local storage.
Here is a super basic example of what you need to do in PHP.
Do your login using javascript/jQuery. Send username/password to login.php for example and authenticate the user like you would normally do.
login.php
use \Firebase\JWT\JWT;
$salt = 'some_salt_string';
$algo = 'HS256'; // allowed ones
$params = array(
"iss" => 'http://example.com', // your own identification
"aud" => ['user_id'=>4324], // client identification
"iat" => time(), // start validity
"exp" => time()+(3600*24) // end validity
);
$token = JWT::encode($params, $salt, $algo);
// example data
$data = [
'example_1'=>'value 1',
'example_2'=>'value 2'
];
$response = array_merge($data,['token'=>$token]);
header('Content-Type: application/json');
echo json_encode($response);
auth.php
use \Firebase\JWT\JWT;
/**
* http://stackoverflow.com/questions/40582161/how-to-properly-use-bearer-tokens
* Get header Authorization
* */
function getAuthorizationHeader(){
$headers = null;
if (isset($_SERVER['Authorization'])) {
$headers = trim($_SERVER["Authorization"]);
}
else if (isset($_SERVER['HTTP_AUTHORIZATION'])) { //Nginx or fast CGI
$headers = trim($_SERVER["HTTP_AUTHORIZATION"]);
} elseif (function_exists('apache_request_headers')) {
$requestHeaders = apache_request_headers();
// Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization)
$requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders));
//print_r($requestHeaders);
if (isset($requestHeaders['Authorization'])) {
$headers = trim($requestHeaders['Authorization']);
}
}
return $headers;
}
/**
* http://stackoverflow.com/questions/40582161/how-to-properly-use-bearer-tokens
* get access token from header
* */
function getBearerToken() {
$headers = $this->getAuthorizationHeader();
// HEADER: Get the access token from the header
if (!empty($headers)) {
if (preg_match('/Bearer\s(\S+)/', $headers, $matches)) {
return $matches[1];
}
}
return null;
}
$token = getBearerToken();
$salt = 'some_salt_string';
$algo = 'HS256';
$decoded_token = JWT::decode($token, $salt, $algo); // returns object
// you can access the audience properties to verify the user id against a requested resource
$user_id = $decoded_token->aud->user_id;
// finally check user id and either deny or allow access
javascript
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-storage-api/1.9.4/jquery.storageapi.min.js"></script>
<script>
var storage=$.localStorage;
function login() {
$.ajax({
url: 'https://example.com/api/login',
type: 'POST',
data: {
username:'Username',
password:'Password'
},
success: function (data) {
storage.set('auth_token',data.token); // store returned token to local storage using the jQuery plugin"value"
},
error: function () {
alert('Error occured');
},
});
}
function testApi() {
$.ajax({
url: 'https://example.com/api/test',
type: 'GET',
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'bearer '+storage.get('auth_token')); // append the token. Mind the space.
},
data: {
query:'value'
},
success: function () {
},
error: function () {
},
});
}
</script>
Include the auth snippet in your secure pages or better said API endpoints.

CORS or cross origin request are to allow other domains to access your web-services. The feature you are asking is user level access or we can say which is public and which is restricted to user roles. It's not something related to CORS. Secondly session is restricted to one system. Please read this page of w3schools for better understanding of session https://www.w3schools.com/php/php_sessions.asp
You can also maintain an user level variable in your mysql or whatever db you are using on the server side to know what is the user level and then bring it to the correct session and with each request check the session value for the type of user like $_SESSION["user_type"] which you can bring from your db and then destroy the session on logout.

Related

Returning back refresh token for google Oauth 2 using php client

I'm using the JS library https://apis.google.com/js/platform.js to log in my user to pass a token to my backend:
gapi.auth2.getAuthInstance().signIn().then(() => {
var user = gapi.auth2.getAuthInstance().currentUser.get();
var authResp = user.getAuthResponse();
var bodyFormData = new FormData();
bodyFormData.append('google_token', authResp.access_token);
When I receive the access token on my server I try use the php client library to generate the rest of the client:
$google = new google();
$google->setAuthConfig('client_secrets.json');
$google->setAccessType('offline');
$google->createAuthUrl();
$google->getRefreshToken();
$google->setAccessToken($thePOSTedAccessToken);
$service = new Google_Service_MyBusiness($google);
The issue is no matter what I do I cannot get my refresh token from client.auth after I create the $client->createAuthUrl(); . So this either means I'm not doing the client properly in php or something else is wrong. I'm asking this question because I've followed all documentation and looked extensively why I'm not receiving my refresh.
You can't mix things like this. If the authorization code was created using JavaScript you will not be able to get a refresh token as JavaScript does not support refresh tokens.
If you want a refresh token then you will need to authorize the user using a server-side language and not a client-side language.
When you you run the code the first time, the user will be displayed with a consent screen. If they consent to your access then you get a code returned. This is an authorization code.
In the example below you can see how the authorization code is exchanged for an access token and refresh token.
Only after $client->authenticate($_GET['code']); is called will $client->getAccessToken(); and $client->getRefreshToken(); work.
Also once the code has been run once, if you try to authorize the user again you will not get a new refresh token, Google assumes that you saved the refresh token they already sent you. If you didn't save it, you would then need to revoke the user's access either using the revoke command in the library or by going to the user's Google account and revoking the application that way.
Sorry, all I have on hand is a web version; not sure if you're running this command-line or web-based.
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/Oauth2Authentication.php';
// Start a session to persist credentials.
session_start();
// Handle authorization flow from the server.
if (! isset($_GET['code'])) {
$client = buildClient();
$auth_url = $client->createAuthUrl();
header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
} else {
$client = buildClient();
$client->authenticate($_GET['code']); // Exchange the authencation code for a refresh token and access token.
// Add access token and refresh token to session.
$_SESSION['access_token'] = $client->getAccessToken();
$_SESSION['refresh_token'] = $client->getRefreshToken();
//Redirect back to main script
$redirect_uri = str_replace("oauth2callback.php",$_SESSION['mainScript'],$client->getRedirectUri());
header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}
?>

HTTP Authorization Header in EventSource (Server Sent Events)

I need to set an Authorization header to an HTML5 EventSource. As Server Sent Events seems to be disused since Websockets appeared, I cannot find any useful documentation. The approach I have already found is to pass the authorization data within the url... but I don't like this method.
I am using AngularJS and set interceptors on $httpProvider, but the EventSource is not intercepted by AngularJS, so I cannot add any header.
I realize your post was over a year ago, but I found myself in the same boat with now good answers. I'm hoping this may help someone, or at least give them some ideas...
Cookies seem easy enough, but what happens if someone is blocking cookies? I would have to prompt them to enable cookies to use the site. At that point they start to wonder if they can trust the site since they disabled cookies for 'security reasons'. All the while, I want cookies enabled for security reasons!
Using AJAX, one can easily POST authentication data over SSL, but that's just not possible with SSE. I've seen many posts where people then say, "just use the querystring", but I don't want to compromise a customer's security by sending the auth data in plain text (example.com/stream?sessionID=idvalue) which someone could snoop.
After racking my brain for a couple hours I realized that I CAN accomplish the the overall goal without compromising the customer's auth data. Just to clarify, I haven't discovered some way to POST when establishing an EventSource connection, but it does allow the browser to securely pass an authentication token with the EventSource each time it reconnects. They key is to get the desired sessionID/token into the lastEventID.
The user can authenticate as usual with a username/password (or by AJAX POSTing a token you keep in localstorage). The AJAX auth process will pass back a JSON object with a short-lived-token (expires in 60 seconds, or when used) which would be saved in your desired backend (eg: mySQL) along with a longer-lasting token. At this point you initiate your SSE connection like:
qString = "?slt=" + "value-that-expires-within-seconds";
streamURL = "http://example.com/stream.php";
var streamSource = new EventSource(streamURL + qString);
streamSource.addEventListener('auth',function(e) {
var authStatus = JSON.parse(e.data);
if (authStatus.session !== 'valid') {
qString = "";
streamSource.close();
}
})
In the corresponding PHP you would do something like this:
header("Content-Type: text/event-stream\n");
ob_end_flush();
ob_start();
if (isThisShortLivedTokenValid($_GET["slt"])) {
// The short-lived-token is still valid... so we will lookup
// the value of the corresponding longer-lasting token and
// IMMEDIATELY invalidate the short-lived-token in the db.
sendMsg($realToken,'auth','session','valid');
exit;
} else if (isThisRealTokenValid($_SERVER["HTTP_LAST_EVENT_ID"])){
while (1) {
// normal code goes here
// if ($someCondition == 'newDataAvailable') sendMsg($realToken,'chat','msg-id','msg-content');
}
} else {
http_response_code(404); // stop the browser from reconnecting.
exit; //quit the PHP script and don't send anything.
}
function sendMsg($id, $event, $key, $val) {
echo "{" . PHP_EOL;
echo "event: " . $event . PHP_EOL;
echo "id: $id" . PHP_EOL;
echo 'data: {"' . $key . '" : "' . $val . '"}' . PHP_EOL;
echo "}" . PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
}
function isThisShortLivedTokenValid($sltValue) {
//stuff to connect to DB and determine if the
//value is still valid for authentication
return $dbResult == $sltValue ? TRUE : FALSE;
}
SSE connects with the short-lived-token, PHP validates against the short-lived-token and deletes it from the DB so it will never be able to AUTH again. This is somewhat similar when you get texted a 6-digit code to login to online banking. We use PHP to push the REAL token (that expires much later) which we retrieved from the database as the event ID. It's not really necessary for Javascript to do anything with this event-- the server will end the connection automatically, but you can listen to the event if you want to do more with it.
At this point, the SSE connection has ended since PHP finished the script. However, the browser will automatically reestablish the connection (usually with 3 seconds). This time, it will send the lastEventId... which we set to the token value before we dropped the connection. On the next connection, this value will be used as our token and the app will run as expected. It's not really necessary to drop the connection as long as you start using the real token as the event-ID when you send messages/events. This token value is transmitted completely encrypted over SSL both when the browser receives it, and in every subsequent connection to the server. The value that was transmitted 'in the clear' was expired within seconds from when we receive & used it and it can no longer be used by anyone that discovers it. If someone does attempt to use it they will receive a 404 RESPONSE.
If you already use the event-stream ID for some other purpose, this may not work 'out of the box' unless you concatenate the auth-token and the previously used value, and split it into variables so it's transparent to the rest of the app. Something like:
// when sending data, send both values
$sseID = $token_value . "_" . $previouslyUsedID;
sendMsg($sseID,'chat','msg-id','msg-content');
// when a new connection is established, break apart the values
$manyIDs = explode("_", $_SERVER["HTTP_LAST_EVENT_ID"])
$token_value = $manyIDs[0]
$previouslyUsedID = $manyIDs[1]
EventSource doesn't have an API for sending HTTP headers to server. I was struggling with this problem too when I was building realtime-chat using SSE.
However I think cookies will be sent automatically if your SSE server is the same server as your authentication server.
This polyfill adds Authorization Header support: https://github.com/Yaffle/EventSource/
So you can do:
new EventSource("https://domain/stream", { authorizationHeader: "Bearer ..." });
The window.EventSource doesn't seem to support passing additional headers yet. Good news is there are some other popular implementations of EventSource that support additional headers. Some of them are as follows:
eventsource
event-source-polyfill
const eventSource = new EventSource(resoureUrl, {
headers: {
'Authorization': 'Bearer ' + authorizationToken
}
});
eventSource.onmessage = result => {
const data = JSON.parse(result.data);
console.log('Data: ', data);
};
eventSource.onerror = err => {
console.log('EventSource error: ', err);
};
If you use this fork of the event-source polyfill you will be able to add authorization headers similarly to the way rafaelzlisboa describes:
https://github.com/AlexGalays/EventSource#923b9a0998fcfd7753040e09aa83764b3cc0230d
Ï don't know if you can provide the authentication header as a second argument like in rafaelzlisboa's example, I got it to work by creating a headers object, and putting my authorization header in there, like this:
new EventSource("https://domain/stream", { headers: { Authorization: Bearer.... }});
the other way to pass auth token is through the URL as query param, but you should take security in consideration. Also add support of authorization through query param on the sever side.
I solved this by additional rest before sse call, this rest is ordinary rest will require the same security protocol that we need for SSE call and get and OTP in response.
And send this OTP to see call query param and validate this OTP in Web filter and replace it with authentication header.
I went through quite a few posts in an attempt to figuring out if the auth token be sent in the EventSource() call. Although there are polyfill alternatives that allow adding headers : https://github.com/whatwg/html/issues/2177
while others mentioned sending auth token over ssl.
Instead of using polyfill EventSource() or sending the auth token in the query params over ssl, send a eventSource identifier (eventSrcUUID) in the params of EventSource url over ssl as follows :-
On user authentication, eventSrcUUID is generated along with the sseEmitter on the server and place in a sseEmitterMap.
Client retrieves the eventSrcUUID from the response and invokes the EventSource() call with the eventSrcUUID in the param. On the server, the sseEmitterMap is referenced to retrieve the the eventSrc object. The sseEmitter object saved in session data is used to send event notifications to client.

Google Oauth Cross-client auth with javascript: Token has expired

I've been dealing all day with this problem and I just can't figure out what's wrong.
I have an application using the Google Api client for javascript which is working with no problems. Now I want to do something on the server side, so after researching for a bit, found that the way to go would be to use the token on the client side with setAccessToken method in the backend.
So I try sending my token object as JSON (using JSON.stringify(gapi.auth.getToken()) ) and once I try doing an API call on the backend that requires auth, I get the following error:
The OAuth 2.0 access token has expired, and a refresh token is not available. Refresh tokens are not returned for responses that were auto-approved.
So, a little puzzled, I try veryfing the token using curl on google's endpoint, which returns the following
{
"issued_to": "client_id",
"audience": "client_id",
"user_id": "user_id",
"scope": "https://www.googleapis.com/auth/youtube.readonly https://www.googleapis.com/auth/youtube.upload https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/plus.me",
"expires_in": 1465,
"email": "user_email",
"verified_email": true,
"access_type": "online"
}
So I know the token is fine and valid. The way my code is setup is as follows (redacted for:
<?php
// The token JSON is not inline, it comes from another source directly from the client side, but this is how it looks
$token_json = '{"state":"","access_token":"TOTALLY_VALID_ACCESS_TOKEN","token_type":"Bearer","expires_in":"3600","scope":"https://www.googleapis.com/auth/youtube.readonly https://www.googleapis.com/auth/youtube.upload https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/plus.me","client_id":"CLIENT_ID","g_user_cookie_policy":"single_host_origin","cookie_policy":"single_host_origin","response_type":"token","issued_at":"1415583001","expires_at":"1415586601","g-oauth-window":{},"status":{"google_logged_in":false,"signed_in":true,"method":"PROMPT"}}';
$OAUTH2_CLIENT_ID = 'CLIENT_ID';
$OAUTH2_CLIENT_SECRET = 'CLIENT_SECRET';
$client = new Google_Client();
$client->setClientId($OAUTH2_CLIENT_ID);
$client->setClientSecret($OAUTH2_CLIENT_SECRET);
$client->setAccessToken($token_json);
$youtube = new Google_Service_YouTube($client);
try{
/* Random code goes here */
// Auth Exception here.
$insertRequest = $youtube->videos->insert("status,snippet", $video);
} catch (Google_Exception $e) {
var_dump($e->getMessage());
}
Do I need to set up offline access or something? I have the requirement that the login process must be in javascript, so no chance of recreating the login flow from the backend.
Is there anything I'm missing?
Well, if someone else stumbles into this:
Apparently, sharing tokens directly is not the way to go, since the different API wrappers handle tokens differently. What you have to do is pass the one-time code to PHP and use the following to acquire an access token
$client = new Google_Client();
$client->setClientId($OAUTH2_CLIENT_ID);
$client->setClientSecret($OAUTH2_CLIENT_SECRET);
// Couldn't find this anywhere in the docs, but it's required. If you don't pass this, you will get an Origin Mismatch error.
$client->setRedirectUri('postmessage');
// Get this from javascript
$client->authenticate($ONE_TIME_CODE);
// Optionally, you might want to retrieve the token and save it somewhere else e.g. the session, as the code is only good for a single use.
$_SESSION['token'] = $client->getAccessToken();
Also, from JS you NEED to specify you require this one-time code besides the JS token, which I couldn't find documented anywhere else as well.
Example settings, for gapi.auth.authorize
{
client_id: CLIENT_ID
scope: APP_SCOPES
cookie_policy: 'single_host_origin'
response_type: 'code token'
immediate: true
}
Thanks a lot, Moustached. I've also spent the whole day trying to solve that. However, I found not that obvious what is one-time code and how to get it, so I decided to provide my code here in case someone else face the same problem:
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="https://apis.google.com/js/platform.js?onload=onGoogleAPILoad" async defer></script>
<script>
$(document).ready(function() {
var auth2;
window.onGoogleAPILoad = function() {
gapi.load('auth2', function() {
auth2 = gapi.auth2.init({ client_id: 'CLIENT_ID', scope: 'profile' });
});
};
$('button').on('click', function() {
auth2.grantOfflineAccess({'redirect_uri': 'postmessage'}).then(function(response) {
$.ajax({
method: 'POST',
url: '/auth/signin',
success: onAuthSuccess = function(response) {
// check response from your server and reload page if succeed
},
data: { code: response.code }
});
});
});
});
</script>
</head>
<body>
<button>Sign In</button>
</body>
</html>
and for back-end the code is almost the same:
$client = new Google_Client();
$client->setClientId('CLIENT_ID');
$client->setClientSecret('CLIENT_SECRET');
$client->setRedirectUri('postmessage');
$client->authenticate($code); // here is one-time code
$plus = new Google_Service_Plus($client);
$user = $plus->people->get('me');
// $user is in array, containing now id, name, etc.
Here is an article on that topic.

Keep getting a "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup" when attempting to google plus login on my web app

I'm trying to implement Google plus sign up on my web app and I followed the google docs to set up the sign up however when I attempt a signup after accepting permissions and using the access token returned to me any api restcall I make returns the Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup error. I have already signed up my app with a ouath 2.0 key, so I don't seem to get what I'm doing wrong. Here is my code.
Cient Side:
const clientId = "5XXX000XX.apps.googleusercontent.com";
const apiKey = "AIzaSyCAXE5JSa36jcC*X7HV40SBcIWBiVGUTBE";
const scopes = "https://www.googleapis.com/auth/plus.login";
let accessToken = null;
function initer() {
gapi.client.setApiKey(apiKey);
// alert("Hello init");
if ($("#authorize-button").length > 0) {
$("#authorize-button").click(onLoginClick);
}
}
function onLoginClick() {
// $("#modalLoading").modal();
// alert("yeah");
gapi.auth.authorize({ client_id: clientId, scope: scopes, immediate: false }, onConnect);
}
function onConnect(authResult) {
// alert("On connect");
if (authResult && !authResult.error) {
alert("Hey");
accessToken = authResult.access_token;
triggerLogin();
} else {
alert("Error");
}
}
triggerLogin = function() {
alert("Triggering login");
$("#modalLoading").modal();
$.ajax({
url: window.config.site_root + "account/google_login",
type: "POST",
data: "access_token=" + accessToken,
success: onLogin,
error() {
onError("Logging In", "starting your session");
},
});
};
onLogin = function(login) {
alert("Login start");
$("#modalLoading").modal("hide");
if (login.operation) {
location.reload();
} else {
alert("Register will start");
triggerRegistration();
}
};
triggerRegistration = function() {
$("#modalLoading").modal();
$.ajax({
url: window.config.site_root + "account/google_registration",
type: "POST",
data: "access_token=" + accessToken,
success: onRegistration,
error() {
alert("An Error");
},
});
};
onRegistration = function(data) {
alert("Handling register");
$("#modalLoading").modal("hide");
if (data.account_exists) {
stage.showErrorModal(
"Account already registered",
"There is already an account with that email address, are you sure you created an account using this login method?",
);
} else if (data.operation) {
alert("Login now");
triggerLogin();
} else {
alert("Error");
onError("Registering", "creating your account");
}
};
Here is my server side code
public function google_registration()
{
$access_token = (isset($_POST["access_token"]) && !empty($_POST["access_token"])) ? $_POST["access_token"] : null;
$name = null;
$email = null;
$account_id = null;
$picture = null;
$gender = null;
try
{
if($access_token)
{
$me = file_get_contents("https://www.googleapis.com/plus/v1/people/me?access_token=".$access_token);
if($me)
{
$me = json_decode($me);
$name = $me->name.formatted;
$email = $me->email;
$account_id = $me->id;
$picture = $me->image;
$gender = ($me->gender == "female") ? 1 : 0;
}
}
}
catch(Exception $error)
{
// let the system handle the error quietly.
}
return $this->service_registration("google", $name, $email, $account_id, $picture, $gender);
}
I too ran into the same error - "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup".
I went and checked my google developer console under APIs for the project associated with the API key/ auth key, eg, https://console.developers.google.com/project/<your app id>/apiui/api. The status for Google+API was set to OFF. I turned it ON.
I then got another access token, and then tried with the new one. It worked, ie, the error was gone and I got the profile details. To cross-check if that was indeed the cause of the error, I went back to console and disabled Google+ API. But now, I get the error:
"Access Not Configured. Please use Google Developers Console to activate the API for your project."
So, I am not 100% sure that it was the turning on/off of the Google+ API in my developer console, but do ensure that this is turned on. Also, wait a few minutes after turning on, and ensure that you get a fresh token each time before trying it.
Make sure you have Google+ Api here enabled.
Without it you will get errors like:
"domain": "usageLimits",
"reason": "dailyLimitExceededUnreg",
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.",
To enable it:
1) open https://console.developers.google.com
2) choose your project (top right corner)
3) search for "Google+ API" in search box and enable it if it is not enabled already.
This issue happens when you are already logged in and still try to login again and again. I faced same error so did some experiments 1) I opened website on my mobile and everything was fine. 2) Then i tried on another laptop and used different gmail account to login and it again worked fine. 3) On my first laptop i tied again by clicking "Signin" button i got same error, so i opened google.com then logged out completely and then tried again, this time it worked. So i believe, Issue is clicking login button again and again without logout.
I am not sure if this is a really a issue, but atleast this is what i found. I am still trying, trying and trying , will post if i found anything else.
Cheers !!
You have to add the apiKey with the URL:
$curl = curl_init( 'https://www.googleapis.com/urlshortener/v1/url?key=AIza3834-Key' );
So I ran into this issue and the above two methods/solutions (enabling API access, and signing out of accounts) did not work for me because for google+ signin you have to pass in the access token in the authorization header of the http call.
Here's how you do it via jQuery:
$.ajax({
type: "GET",
url: "https://www.googleapis.com/plus/v1/people/me",
headers: {
"Authorization":"Bearer " + {access_token},
}
});
It seems your issue is with the server side code where you pass in access_token in the params (which is not necessary).
Here's my attempt on what the PHP implementation would look like:
$opts = array(
'http'=>array(
'method'=>"GET",
'header'=>"Authorization: Bearer ".$access_token
)
);
$context = stream_context_create($opts);
// Open the file using the HTTP headers set above
$file = file_get_contents('https://www.googleapis.com/plus/v1/people/me', false, $context);'
Hopefully you're sending that access token over https! It might be worth considering using the code instead and doing an exchange on the server side for an access token, for improved security if nothing else, there's some documentation on that approach here: https://developers.google.com/+/web/signin/server-side-flow
With regards to the problem you're seeing, it seems like the access token is bad, or not making it through correctly. Can you check the access token that you receive against the tokeninfo endpoint: https://www.googleapis.com/oauth2/v1/tokeninfo?access_token= - that should show valid information. Nothing stands out as being off in the code, but if the token is getting mangled you might see a similar error.
I got have the same issue, The solution is: set APIKEY
I was also desperate, and finally I managed to find a solution. The only problem is to add the correct api token linked to your app, in my case was the browser token, and it all works.
Example: I wanted to have all the events associated with my calendar
https://www.googleapis.com/calendar/v3/calendars/{calendar_id}/events?&key={api_key}
maybe I will help someone who is having the same problem.
Good Luck
I was trying to use access_token to fetch a returning user's name and picture when they already logged in during a prior session. After running into the "unauthenticated use" error message, this is what finally worked via cURL in PHP.
//initialize provider specific params for oauth2
$access_token = <fetched from app database from last login> //remember to never expose this to users
$api_url = 'https://www.googleapis.com/plus/v1/people/me';
$headers = [
'Authorization: Bearer '.$access_token
];
//send curl request
$curl = curl_init();
$curl_opts = [
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_POST => 0, //GET
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => 1,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_URL => $api_url,
CURLOPT_HTTPHEADER => $headers,
];
curl_setopt_array($curl, $curl_opts);
$curl_response = json_decode(curl_exec($curl), true);
$curl_status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
//parse curl response from provider into name and icon fields for my app
$name = $curl_response['displayName'];
$icon = $curl_response['image']['url'];
Note that https://www.googleapis.com/plus/v1/people/me (for returning users via access_token) and https://www.googleapis.com/oauth2/v4/token (for initial login via code) return the user name and picture in different fields/structures.
I had a problem on JavaScript side (Client Side).
In my case it was necessary to add API_KEY to gapi.client.init()
Full my function:
async function initClient () {
return new Promise((resolve, reject) => {
const API_KEY = "YOUR_GOOGLE_API_KEY"
const CLIENT_ID = "YOUR_GOOGLE_OAUTH2_CLIENT_ID"
const DISCOVERY_DOCS = ['https://www.googleapis.com/discovery/v1/apis/youtube/v3/rest']
const SCOPES = 'https://www.googleapis.com/auth/youtube.readonly'
const initData = { apiKey: API_KEY, clientId: CLIENT_ID, discoveryDocs: DISCOVERY_DOCS, scope: SCOPES }
gapi.client.init(initData).then(function () {
// YOUR CODE HERE
}, function (error) {
reject(new Error('Reject Error: ' + error))
})
.catch(err => console.log('Catch Error', err))
})
}
API_KEY and CLIENT_ID can be taken from here (Google Cloud Platform -> APIs & Services -> Credentials -> Credentials [Tab])
In my case, it happened at a specific time of the day, After spending few hours finally found that my daily quota limit (Queries per 100 seconds) was exceeded at that time because of a high number of requests. so it was throwing the error. I have contacted google support to increase them.
In my case, it was because I was using an old access token. You must keep in mind that access tokens have a short life span, so you must use the refresh token to generate a new access token. Example (using a PHP Class):
// Get the access token from DB or filesystem
$accessToken = $this->getAccessToken();
// Set the access token
$this->client->setAccessToken($accessToken);
// Refresh the token if it's expired.
if ($this->client->isAccessTokenExpired()) {
$this->client->fetchAccessTokenWithRefreshToken($this->client->getRefreshToken());
// Save the new access token to DB or filesystem
$this->saveAccessToken($this->client->getAccessToken());
}
I just had same problem and found out what was wrong.
When you enable your Google Plus API from your Library, there is extra steps to completely activate. You have to click "Manage" button in the Google Plus API Library page and fill up two question answer and submit. That's all!!
This fixed my issue and I hope its fixes yours as well. Here is the screenshot of that page where i was taken after I clicked "Manage" button which says "Add credentials to your project".
I'm getting the following error:
{
"error": {
"errors": [
{
"domain": "usageLimits",
"reason": "dailyLimitExceededUnreg",
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use
requires signup.",
"extendedHelp": "https://code.google.com/apis/console"
}
],
"code": 403,
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use
requires signup."
}
}
I'm not sure but i think its caused in my case on the OAuth consent Screen of the Google Developers Console, there is this message,
The user cap limits the number of users that can grant permission to your app when requesting unapproved sensitive or restricted scopes. The user cap applies over the entire lifetime of the project, and it cannot be reset or changed. Verified apps will still display the user cap on this page, but the user cap does not apply if you are requesting only approved sensitive or restricted scopes. If your users are seeing the "unverified app" screen , it is because your OAuth request includes additional scopes that haven't been approved.
I'm requesting restricted scopes:
private val SCOPES: List<String> = arrayListOf(DriveScopes.DRIVE_FILE, DriveScopes.DRIVE_APPDATA, DriveScopes.DRIVE)
Although my app is in development it seems to require verification, I think over the lifetime of the development of my app i have signed in over 100 times. (See OAuth user quotas)
https://support.google.com/cloud/answer/7454865?authuser=0
I Guess i have to verify my App Does anyone know if this is the case...?
I think (at least for some) this error could be leading them in the wrong direction. In my case, my API call was working, then suddenly stopped. Here was my thought process while working through this error, hopefully it will help someone:
OBSERVATIONS
New error was produced
"error": {
"errors": [
{
"domain": "usageLimits",
"reason": "dailyLimitExceededUnreg",
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.",
"extendedHelp": "https://code.google.com/apis/console"
}
],
"code": 403,
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup."
}
}
This occurred during a test attempting to send an update to the Google Calendar API with the following call:
service.events().patch(calendarId='primary', eventId=id, sendNotifications=True, body=update).execute()
This occurred shortly after I updated the data in my time_start variable referenced in the json that gets sent to google:
update = {
'start': {
'dateTime': time_start, # Specifically this variable (time_start) was changed
'timeZone': time_zone,
},
'end': {
'dateTime': time_end,
'timeZone': time_zone,
},
'description': description,
}
The information in my time_start variable changed from:
2020-05-13T17:06:42-07:00 to 2020-05-13T17:06:42 (note the missing UTC offset)
ACTIONS
Updated my code populating time_start to include the missing UTC offset that was being passed to google calendar API.
RESULTS
The payload successfully transmitted to google calendar and my event was patched with the update
Just in case someone has gotten this issue while using Drive API (in my case, using Python as programming language), this can also occur due to an incorrect metadata update. Here is my error report:
"error": {
"errors": [
{
"domain": "usageLimits",
"reason": "dailyLimitExceededUnreg",
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.",
"extendedHelp": "https://code.google.com/apis/console"
}
],
"code": 403,
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup."
}
}
This was, in essence, due to the fact that I was trying to alter both copyRequiresWriterPermission and viewersCanCopyContent fields over an application/vnd.google-apps.folder, which is not allowed.
No clue about why Google chose to send an error report so unrelated to the actual error.
This is because the scope you use for Drive API is labelled restricted.
You can make a limited number of requests to get file data/content.
Once you have reached the limit you have to get another access_token for the existing session. It is like having refresh_token.
You can wrap your code that sends requests to get files in try...catch
If that error is thrown you can requestAccessToken.
NOTE: For refreshing the access token in an existing session, you do not need to ask for content again.

How do I renew a Facebook user_access_token if I deal with a lot of AJAX?

Please tell me if I'm understanding correctly. (because I might not be.)
User posts something on my site. (He checked "also post to Facebook".)
Client sends an AJAX POST request to my server, and my server inserts the record in my database.
The server realizes the the facebook user access token is expired, so it sends the response back to the client, while storing the post in a session.
The client does a window.location.replace(facebook_oauth_dialog_url)
Then the user will see a sudden "flash", going to Facebook, then coming back to the website. My server picks up the new access token.
My server checks the session to see what should be posted to Facebook. And then, it uses the new access token to post that to Facebook.
Is it really this tedious? Why can't I renew the app server-side without the user going through the dialog?
My entire site is Backbone.js. That means, it's one big page. I can't jump the user back and forth between Facebook and my website like this.
The idea is to make use of the Facebook JS-SDK methods:
User check the Post To Facebook option
you check if the current user is connected to your app (using FB.getLoginStatus())
if the user is connected, you have two options:
post directly using the FB.api method or
Send the access_token to your server to complete the post process there
if the user is not connected (or not logged in to Facebook), use the FB.login() method
Here's a quick example (with a Live Demo!) for you to get started:
<!DOCTYPE html>
<html xmlns:fb="http://www.facebook.com/2008/fbml">
<body>
<div id="fb-root"></div>
<script>
var fbLoaded = false;
window.fbAsyncInit = function() {
FB.init({
appId : 'YOUR_APP_ID', // App ID
//channelUrl : '//WWW.YOUR_DOMAIN.COM/channel.html', // Channel File
status : true, // check login status
cookie : true, // enable cookies to allow the server to access the session
xfbml : true // parse XFBML
});
fbLoaded = true;
// Additional initialization code here
};
function postForm() {
var msg = document.myForm.msg.value;
// do form validation here, e.g:
if(!msg.length) {
alert("You should enter a message!");
return false;
}
// do we need to post to Facebook?
if(document.myForm.toFB.checked) {
// is the library loaded?
if(!fbLoaded) {
alert("Facebook JS-SDK is not yet loaded. Please try again later or uncheck Post To Facebook option");
return false;
}
FB.getLoginStatus(function(response) {
if (response.status === 'connected') {
var uid = response.authResponse.userID;
var accessToken = response.authResponse.accessToken;
/*
* message can be posted to Facebook directly
* using the FB.api method or accessToken
* can be sent to the server and do the call
* from there
*/
myAjaxCall(msg, accessToken);
} else {
// status is either not_authorized or unknown
FB.login(function(response) {
if (response.authResponse) {
var accessToken = response.authResponse.accessToken;
myAjaxCall(msg, accessToken);
} else {
alert('User cancelled login or did not fully authorize.');
}
}, {scope: 'publish_stream'});
}
});
} else {
myAjaxCall(msg);
}
return false;
}
function myAjaxCall(m,a) {
alert("Here you make the ajax call\nMessage: " + m + "\nAccess Token: " + a);
}
// Load the SDK Asynchronously
(function(d){
var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) {return;}
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
ref.parentNode.insertBefore(js, ref);
}(document));
</script>
<form id="myForm" name="myForm" action="post" onSubmit="return postForm()">
<p><label>Your Message:</label><br/><textarea name="msg"></textarea></p>
<p><label>Post to Facebook?</label><input type="checkbox" value="1" name="toFB" /></p>
<p><input type="submit" value="Submit"></p>
</form>
</body>
</html>
Before posting to the server, call FB.getLoginStatus() on the client to get the latest access token. There is no flash when using this method and no user interaction, as it simply grabs a new access token.
FB.getLoginStatus( function ( response ) {
if ( response.authResponse ) {
var accessToken = response.authResponse.accessToken;
//post to server
};
} );
I hope you known that if you have publish_stream permission you don't need access token here is documentation for publish_stream and Below is the solution for four scenarios
1.The token expires after expires time (2 hours is the default).
2.The user changes his/her password which invalidates the access token.
3.The user de-authorizes your app.
4.The user logs out of Facebook.
To ensure the best experience for your users, your app needs to be prepared to catch errors for the above scenarios. The following PHP code shows you how to handle these errors and retrieve a new access token.
When you redirect the user to the auth dialog, the user is not prompted for permissions if the user has already authorized your application. Facebook will return you a valid access token without any user facing dialog. However if the user has de-authorized your application then the user will need to re-authorize your application for you to get the access_token.
<?php
$app_id = "YOUR_APP_ID";
$app_secret = "YOUR_APP_SECRET";
$my_url = "YOUR_POST_LOGIN_URL";
// known valid access token stored in a database
$access_token = "YOUR_STORED_ACCESS_TOKEN";
$code = $_REQUEST["code"];
// If we get a code, it means that we have re-authed the user
//and can get a valid access_token.
if (isset($code)) {
$token_url="https://graph.facebook.com/oauth/access_token?client_id="
. $app_id . "&redirect_uri=" . urlencode($my_url)
. "&client_secret=" . $app_secret
. "&code=" . $code . "&display=popup";
$response = file_get_contents($token_url);
$params = null;
parse_str($response, $params);
$access_token = $params['access_token'];
}
// Attempt to query the graph:
$graph_url = "https://graph.facebook.com/me?"
. "access_token=" . $access_token;
$response = curl_get_file_contents($graph_url);
$decoded_response = json_decode($response);
//Check for errors
if ($decoded_response->error) {
// check to see if this is an oAuth error:
if ($decoded_response->error->type== "OAuthException") {
// Retrieving a valid access token.
$dialog_url= "https://www.facebook.com/dialog/oauth?"
. "client_id=" . $app_id
. "&redirect_uri=" . urlencode($my_url);
echo("<script> top.location.href='" . $dialog_url
. "'</script>");
}
else {
echo "other error has happened";
}
}
else {
// success
echo("success" . $decoded_response->name);
echo($access_token);
}
// note this wrapper function exists in order to circumvent PHP’s
//strict obeying of HTTP error codes. In this case, Facebook
//returns error code 400 which PHP obeys and wipes out
//the response.
function curl_get_file_contents($URL) {
$c = curl_init();
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($c, CURLOPT_URL, $URL);
$contents = curl_exec($c);
$err = curl_getinfo($c,CURLINFO_HTTP_CODE);
curl_close($c);
if ($contents) return $contents;
else return FALSE;
}
?>
for more details info you can visit this link
Thanks
UPDATE
well you are doing something wrong you do not need to renew access token even if its expired all you need is sending user facebook id & content you want to post together to your server by ajax then post it without access token Just check up here
Publish Stream from the application - for non logged in user, using Graph API, php SDK
if you have publish_stream permission you dont need access token
this is documentation for publish_stream
https://developers.facebook.com/docs/reference/api/permissions/
Enables your app to post content, comments, and likes to a user's
stream and to the streams of the user's friends. With this permission,
you can publish content to a user's feed at any time, without
requiring offline_access. However, please note that Facebook
recommends a user-initiated sharing model.
I've got the problem in another project.
The way I handle it is to create a hidden iframe. The first time you need the user to accept the privilege, use your main window to redirect. then, when you are sure that the user has already accepted the privilege, use the hidden iframe to communicate with facebook.
The user will not see the "flash" because it will done in an iframe.
I've done it with GWT. Here is the code I used : it communicates with Facebook via the iframe and do a check on access token every 500ms to see if the token is valid.
The code is in java (compiled in javascript using gwt).
public class FacebookConnector extends Composite
{
public static final String ARG_ACCESS_TOKEN_EXPIRES = "fb_accessTokenExpires";
public static final String ARG_GAME_FACEBOOK_NAME = "gameFBName";
public static final String ARG_GAME_FACEBOOK_ID = "gameFBId";
private static FacebookConnectorUiBinder uiBinder = GWT.create(FacebookConnectorUiBinder.class);
interface FacebookConnectorUiBinder extends UiBinder<Widget, FacebookConnector>
{
}
private static FacebookConnector me;
public static FacebookConnector getInstance()
{
if (me == null)
{
me = new FacebookConnector();
}
return me;
}
#UiField
IFrameElement iframe;
private Date accessToken;
private Timer timer;
protected FacebookConnector()
{
initWidget(uiBinder.createAndBindUi(this));
if (ArgManager.getArg(ARG_ACCESS_TOKEN_EXPIRES) != null)
{
accessToken = new Date(Long.parseLong(ArgManager.getArg(ARG_ACCESS_TOKEN_EXPIRES)));
}
}
public void checkAccessToken(final AbstractAsyncCallback<Void> callback)
{
if (accessToken == null || accessToken.before(new Date()))
{
// send authentication
String url = "https://graph.facebook.com/oauth/authorize?client_id="
+ ArgManager.getArg(ARG_GAME_FACEBOOK_ID) + "&scope=user_birthday,email&redirect_uri="
+ ArgManager.getArg(ArgManager.ARG_URL_FACEBOOK_BASE) + "page/facebook-step2%3FgameName%3D"
+ ArgManager.getGameShortcut();
iframe.setSrc(url);
// check url
timer = new Timer() {
#Override
public void run()
{
ClientFactory.getInstance().getService().getAccessTokenExpires(new AbstractAsyncCallback<Date>() {
#Override
public void onSuccess(Date result)
{
super.onSuccess(result);
if (result != null && result.after(new Date()))
{
accessToken = result;
// call the callback
callback.onSuccess(null);
}
else
{
// try again in one second
timer.schedule(1000);
}
}
});
}
};
// launch timer in 500 milliseconds
timer.schedule(500);
}
else
{
callback.onSuccess(null);
}
}
}
Hope it will help you.
You can't simply do a server side exchange because that bypasses the user's control of the authorization.
Like others have said, you should use the javascript sdk to facilitate updating the access token. By default, it uses an iframe and falls back on a popup to handle communicating with Facebook. This should work well with your backbone.js application.
I like to define a javascript function that takes success and denied callbacks to execute after checking the facebook auth status:
function checkFBAuth(success, denied, scope) {
FB.getLoginStatus(function (response) {
if (response.status === 'connected') {
success(response);
} else {
FB.login(function(response) {
if (response.status === 'connected') {
success(response);
} else {
denied(response);
}
}, scope);
}
});
};
This will go ahead and run FB.login if the user's session has expired. In your success callback, you could also pass response.authResponse.signedRequest as signed_request in your AJAX POST data. This will allow most FB SDK's (for example, the PHP SDK) to recognize and validate the signed request and set the user id and access token. You could also pass the whole response.authResponse data with your POST. That has the accessToken, userID, and expiresIn time.
See https://developers.facebook.com/docs/reference/javascript/FB.getLoginStatus/ for the docs on the Facebook Developers site.
Also, if you enable the offline access deprecation migration, you can exchange access token to extend the expiration date to 60 days instead of the default 2 hours. See https://developers.facebook.com/docs/offline-access-deprecation/
Like #ThinkingStiff said, the key point is that you need to call FB.getLoginStatus() on the client to get the latest access token
These days, all the cool kids are handling their logins and retrieving their access tokens via JavaScript with the SDK. And hey, why not? The users love it!
After the JavaScript SDK retrieves the access token, all AJAX requests to your server will have access to that access token as well. That is, it is automatically passed along with each
AJAX request in the form of a cookie.
So, on the server side, you can retrieve the access token via cookies (our friend StackOverflow has some Answers related to finding that cookie). However, if you do the other cool thing and use the PHP SDK you won't even have to give it a second thought, because it will automatically grab the cookie for you, if it's there!

Categories

Resources