secret key visible in javascript code (JavaScript, Firebase)? - javascript

I have a function that checks wether the password and user match before authenticating using a custom token.
function getUser(user, password) {
var usersRef = new Firebase("mydatabase/users");
var userRef = usersRef.child(user);
userRef.once("value",
function getHandler(snapshot) {
if (snapshot.val().password == password) {
var token = createToken(user);
ref.authWithCustomToken(token, authHandler);
} else {
alert("Gebruikersnaam en code komen niet overeen");
}
},
function errorHandler(errorObject) {
alert("Ophalen van gebruikersgegevens is mislukt: " + errorObject.code);
}
);
}
To create e token I instantiate the firebase class FirebaseTokenGenerator with the secret key. Like
function createToken(user) {
var tokenGenerator = new FirebaseTokenGenerator("<secret key...>");
var updatedObj = {
"uid": "custom:"+user,
"level": "docent"
}
return tokenGenerator.createToken(updatedObj);
However in that way the secret key would be visible for anyone looking into the .js source code. I'm pretty sure that is not how it should be done but what is the right way to do this then, the Firebase-way?
EDIT:
I tried to figure out the javascript way for this but got stuck there so switched back to php. Used the firebase-token-generator code from github (here), installed it including dependencies in my project with composer and that all seems to work fine (token is generated).
<?php
include_once "FirebaseToken.php";
$uid = $_POST['uid'];
$level = $_POST['level'];
$tokenGen = new Services_FirebaseTokenGenerator("<secret key>");
$token = $tokenGen->createToken(array("uid" => "custom:BAAJ"), array("admin" => False));
echo $token;
?>
Reading this SO post I found that following would be the way to embed it in my javascript code:
function createToken(user) {
$.post('php/createtoken.php', {uid: user, level: 'docent'}, function(data){
//successful ajax request
return data;
}).error(function(error){
alert("Create token mislukt: "+error);
});
};
But for som reason the token is not generated then. When called from getUser at first both the success and error part of the javascript createToken function are not executed at all (resulting in an undefined value for variable token. Then createToken is called a second time (??) and then the success part is executed however the data now does not contain the token but the complete php script...?
What is the problem and how to solve?

You should avoid to do anything 'secret' in the client browser. There, the secret will be visible, password will be hackable via XSS and sniffable and you could connect to any user with some XSS.
You should rather move this part onto the server side to be more secure, it could be NodeJS, PHP or anything else. (HTTPS connections, obviously)

You have to do this in your server side application and send the generated token to your client app. The token should be sent over HTTPS as well. Default token expiry is 24 hours. This can be changed.

Related

Google Sign In for Websites causing "Signature verification failed" with JWT PHP library

Google web sign in has driven me positively crazy...
I'm building a simple web application, and I'm trying to integrate Google's sign in feature into the website (https://developers.google.com/identity/sign-in/web/).
The JavaScript seemed to go fairly well, and the next step was to verify the id_token I was receiving with my backend server (again, against Google's recommendation: https://developers.google.com/identity/sign-in/web/backend-auth).
It's a PHP-based web application, and I've successfully installed the Google Client API library using composer: composer require google/apiclient, but when posting my id_token value to my PHP backend system I'm consistently receiving the following error:
Firebase\JWT\SignatureInvalidException
File: .\vendor\firebase\php-jwt\src\JWT.php:112
Message: Signature verification failed
Stack trace:
#0 .\vendor\google\apiclient\src\Google\AccessToken\Verify.php(103): Firebase\JWT\JWT::decode('eyJhbGciOiJSUzI...', '-----BEGIN PUBL...', Array)
#1 .\vendor\google\apiclient\src\Google\Client.php(712): Google_AccessToken_Verify->verifyIdToken('eyJhbGciOiJSUzI...', '10...')
I've also used the id_token value on Google's "tokeninfo" endpoint (https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=ABC123), and the id_token validates perfectly, so I'm sure it's not the id_token value that's wrong. It's also passing it perfectly via the POST variable to the PHP script, so I'm at a bit of a loss.
Here's my code:
Javascript:
<script src="https://apis.google.com/js/platform.js?onload=googleAppStart" async defer></script>
<script>
var googleAppStart = function(){gapi.load('auth2', initGoogleSignIn);};
var auth = false;
function initGoogleSignIn(){
auth = gapi.auth2.init({
client_id : 'client-id-is-here',
scope : 'profile'
});
auth.attachClickHandler(document.getElementById('my-button'));
auth.isSignedIn.listen(googleSignInChanged);
auth.currentUser.listen(googleCurrentUserChanged);
if (auth.isSignedIn.get() == true)
auth.signIn();
}
function googleSignInChanged(signed_in){}
function googleCurrentUserChanged(user){
var auth_response = user.getAuthResponse();
var id_token = auth_response.id_token;
if (id_token != undefined){
var url = '/verify-google-signin';
var params = {id_token: id_token};
jQuery.post(url, params, function(data){}, 'json');
}
}
</script>
...and my PHP catching the POST:
<?php
require_once '/vendor/autoload.php';
$credentials = array("client_id" => "client-id-is-here");
$client = new \Google_Client($credentials);
$data = $_POST;
if (isset($data['id_token'])) {
$id_token = trim($data['id_token']);
// Exception generated here...
$payload = $client->verifyIdToken($id_token);
}
?>
Thank you so much for taking the time to read this, and for any assistance! It's greatly appreciated!
I had the same issue today.
Easier if you just execute:
composer require firebase/php-jwt:4.0
Fortunately you can verify id_token without google library as described here https://developers.google.com/identity/sign-in/web/backend-auth#calling-the-tokeninfo-endpoint
if (isset($data['id_token'])) {
$id_token = trim($data['id_token']);
try {
$res = (new \GuzzleHttp\Client())->request('GET',
'https://www.googleapis.com/oauth2/v3/tokeninfo', [
'query' => ['id_token' => $id_token],
]);
$payload = json_decode($res->getBody()->getContents(), true);
//you still need to check that the aud claim contains one of your app's client IDs
if ($payload['aud'] != "client-id-is-here") {
throw new \Exception("App Isn't valid", 422);
}
} catch (RequestException $e) {
//IF token isn't valid you will be here
if ($e->hasResponse()) {
/** #var \GuzzleHttp\Psr7\Response $resp */
$resp = $e->getResponse();
$error = json_decode($resp->getBody()->getContents(), true)['error_description'];
throw new \Exception($error, $resp->getStatusCode());
}
}
}
If you have no exceptions then your token is valid
It is a problem with php-jwt. Latest version is not working with Google Api Client.
Try to use php-jwt version 4.
I put "firebase/php-jwt": "<5.0" in my composer.json file.
Worked as a charm!
This has been fixed in v2.2.1 of google/apiclient so make sure you are running this version or later if anyone else encounters this issue.
Related discussions here and here.
I've got problems with Google Sign-In backend authentication yesterday, using both google/apiclient 2.2.0 and 2.1.3.
tl;dr it was most likely malfunctions on the Google side or some obscure limits I'm unaware of (nothing in the Developer Console about that).
First, the "idToken" Google was giving me client-side was not a valid JWT: openssl_verify() was rejecting it in Firebase\JWT\JWT, throwing a Firebase\JWT\SignatureInvalidException. I followed your advice, installed google/apiclient 2.1.3 and this exception was not being throwed anymore but the resulting payload was null (so the idToken was still invalid).
A few hours later, I had experienced intermittent results with the apiclient 2.3.0: sometimes the token was invalidated by signature verification (and throwing the signature exception) and sometimes the token was cryptographically valid but the returned payload was null. Once in a while, the token was valid (!).
In the end, the backend authenticaton process was succeeding every time.
As I began experiencing these problems, I tried to fix it generating new OAuth2 keys, revert to a previous verions of my codebase (both server-side and client-side) that I knew was working, removed all browser data and tried to get the token on Cordova with the Sign In for Android. Nothing worked. Also no message in the Developer Console, no visible limits, no security e-mail.
If it's not a bug but a feature, the error handling is quite harsh :)
If you are not using Firebase (taken from https://github.com/googleapis/google-api-php-client)
rm -r vendor/google/apiclient-services
Add this to composer.json
"scripts": {
"post-update-cmd": "Google\\Task\\Composer::cleanup"
},
"extra": {
"google/apiclient-services": [
"Drive" ,
"... any other services here ...",
]
}
Finally
composer install
This will resolve the error message with the added benefit of drastically reducing installed packages. As noted, there are at least 200 API services for GAPI

Creating a user session - NODE js

I am new to node js & javascript in general. I have the below piece of code that will handle a login. I have a MYSQL database with a customer table. When the customer enters their username and password, it checks does it exist in the database. This part is working.
I now want to enhance this feature so that it will take the username and create some sort of a session variable, which can be used across the application. I am new to JS so I am not yet sure which inbuilt facilities already exist, or best practice around sessions.
I want to be able to use this session variable across the application, and for subsequent logout facility.
Can someone advise me on this, or point me in the right direction? Thanks.
case "/login":
var body = '';
console.log("user Login ");
request.on('data', function (data) {
body += data;
});
request.on('end', function () {
var obj = JSON.parse(body);
console.log(JSON.stringify(obj, null, 2));
var query = "SELECT * FROM Customer where name='"+obj.name+"'";
response.writeHead(200, {
'Access-Control-Allow-Origin': '*'
});
db.query(
query,
[],
function(err, rows) {
if (err) {
response.end('{"error": "1"}');
throw err;
}
if (rows!=null && rows.length>0) {
console.log(" user in database" );
theuserid = rows[0].customerID;
var obj = {
id: theuserid
}
response.end(JSON.stringify(obj));
}
else{
response.end('{"error": "1"}');
console.log(" user not in database");
}
}
);
});
}
There can be multiple ways of implementing a user session.
One, you could use a browser cookie, it comes with many pros and cons and you should read about it a bit to see how its managed. This would also depend on the server you are using (express, hapi, etc).
Two, you can set a JWT token on the backend, and include it in the header of the response, then you can either use your application state or the local storage of the browser to save that token on the UI. Any such follow up requests requiring authentication should contain this auth token as a header for verification.
For more clarity, you can look into related libraries (such as passport), which make this task a lot easier.
PS: If you choose cookies, please make sure the business is going to allow it or not as the end-users do not like being tracked always. :)

Ratchet PHP Websockets: Private messaging (control who messages are being sent to)

Got another question.
I am starting to get used to how the websocket works.
I have even managed to implement communication cross domain.
But now I have yet to hit another milestone.
Here is a snippet from my current implementation
public function onMessage(ConnectionInterface $conn, $msg)
{
$msgjson = json_decode($msg);
$tag = $msgjson->tag;
global $users;
if($tag == "[msgsend]")
{
foreach($this->clients as $client)
{
$client->send($msg);
}
}
else if($tag == "[bye]")
{
foreach($this->clients as $client)
{
$client->send($msg);
}
foreach($users as $key => $user)
{
if($user->name == $msgjson->uname)
{
unset($users[$key]);
}
}
$this->clients->detach($conn);
}
else if($tag == "[connected]")
{
//store client information
$temp = new Users();
$temp->name = $msgjson->uname;
$temp->connection = $conn;
$temp->timestamp = new \DateTime();
$users[] = $temp;
usort($users, array($this, "cmp"));
//send out messages
foreach($this->clients as $client)
{
$client->send($msg);
}
}
else if($tag == "[imalive]")
{
//update user timestamp who sent [imalive]
global $users;
foreach($users as $user)
{
if($msgjson->uname == $user->name)
{
$user->timestamp = new \DateTime();
}
}
}
}
Now my question is. As we can see, in the onMessage() function, and tutorials that I have done, I know how to read and parse JSON data, make sense of messages, tell who the message is coming from ($conn).....
But say when I send a message in a JSON packet, I want to include the nicknames of who the message is from as well as who the message is going to.
This will allow me to implement private instant messaging in both my social network that i am building and in chatrooms.
Instead of a for loop sending messages to all connected clients, I only want to send messages to specific ones. I know clients have a property ($this->$client->resourceID or something along those lines) but not sure how to incorporate it as a solution either. I also want users to maintain a connection as they jump to different pages on the website and even after a refresh, still be able to continue messaging. I assume each refresh disconnects the client. So I have to have a way where the server can tell each time who is who and where messages are coming from and where they are going to.
But yeah, private messaging. I dont want to have to send unnessecary messages to everyone or unintended targets. How can I accomplish this?
I hope my question makes sense. thanks.
Being able to uniquely identify users that connect to your WebSocket server and then being able to target those users, specifically, when sending out messages needs to begin at the onOpen callback where the connection is actually negotiated with the server.
In your onOpen method you should have some way of uniquely identifying the user on your system via some user id that's globally stored in your database, or persistence store. Since the connection is negotiated over HTTP you have access to the HTTP request via the $conn->WebSocket->request, which is a GuzzleHttp object containing the client's HTTP request information. You could use this to pull a cookie, for example, that contains some user id data or token that you can compare to your database to figure out who the user is and then store that as property of the $client object.
For now, let's pretend you're writing a normal PHP script where you do user authentication over HTTP and store the user id in a session. This session sets a cookie on the client's machine (by default the cookie name is the session name, which is usually PHPSESSID unless you change it) containing a session id. This session id can be used in your WebSocket server to accessed the session storage the same way you do normally in PHP.
Here's a simple example, where we expect a cookie named PHPSESSID from the request to capture the session id from cookie.
public function onOpen(ConnectionInterface $conn) {
// extract the cookie header from the HTTP request as a string
$cookies = (string) $conn->WebSocket->request->getHeader('Cookie');
// look at each cookie to find the one you expect
$cookies = array_map('trim', explode(';', $cookies));
$sessionId = null;
foreach($cookies as $cookie) {
// If the string is empty keep going
if (!strlen($cookie)) {
continue;
}
// Otherwise, let's get the cookie name and value
list($cookieName, $cookieValue) = explode('=', $cookie, 2) + [null, null];
// If either are empty, something went wrong, we'll fail silently here
if (!strlen($cookieName) || !strlen($cookieValue)) {
continue;
}
// If it's not the cookie we're looking for keep going
if ($cookieName !== "PHPSESSID") {
continue;
}
// If we've gotten this far we have the session id
$sessionId = urldecode($cookieValue);
break;
}
// If we got here and $sessionId is still null, then the user isn't logged in
if (!$sessionId) {
return $conn->close(); // close the connection - no session!
}
}
Now that you actually have the $sessionId you can use it to access the session storage for that session, pull the session information into your WebSocket server and store it as a property of the client connection object $conn.
So continuing from the above example let's add this code to the onOpen method.
public function onOpen(ConnectionInterface $conn) {
$conn->session = $this->methodToGetSessionData($sessionId);
// now you have access to things in the session
$this->clinets[] = $conn;
}
Now, let's go back to your example where you want to send a message specifically to just one user. Let's assume we have the following properties stored in the session, which are now accessible from the client connection object...
$conn->session->userName = 'Bob';
$conn->session->userId = 1;
So let's say Bob wants to send a message to Jane. A request comes in to your WS server with something like {"from":1,"to":2,tag:"[msgsend]"} where the to and from properties of that JSON are basically the user ids of the user the message is from and the user the message is intended to be sent to, respectively. Let's assume Jane is userId = 2 for this example.
public function onMessage(ConnectionInterface $conn, $msg) {
$msgjson = json_decode($msg);
$tag = $msgjson->tag;
if ($tag == '[msgsend]') {
foreach($this->clients as $client) {
// only send to the designated recipient user id
if ($msgjson->to == $client->session->userId) {
$client->send($msg);
}
}
}
}
Obviously you might want to have more elaborate validation in there, but you should be able to expand on this from here.

Use the Facebook JS SDK, how can I easily authenticate and use the PHP SDK too?

I have built a Facebook app using both the PHP and JS SDKs, but it feels as though the way I have built it is simply wrong, but I'm struggling to find any clear-cut answers (and after reading a few posts and comments on this, I'm not alone).
This is a Canvas app using CodeIgniter and SDK 5.0.0.
I have a few requirements from FB for this app:
Log the user in, and keep them logged in for the duration of their session
Check the logged in status, and if they log out for any reason (end of session perhaps?) the JS SDK should re-prompt for login and not allow further progess (this is fine to happen on page reload)
Get access to /me within PHP for various bits and bobs
I currently have the following JS on every page:
window.fbAsyncInit = function() {
FB.init({
appId : '#####',
xfbml : true,
cookie : true,
version : 'v2.4'
});
loaded();
FB.Canvas.startTimer();
FB.getLoginStatus(function(response) {
// Check login status on load, and if the user is
// already logged in, go directly to the welcome message.
if (response.status == 'connected') {
// Logged in already
} else {
// Otherwise, ask them to log in
window.top.location = '/login.php';
}
});
};
That works fine. Login.php has some JS that handles logging in, which also works great.
The problem arises with PHP. I have a controller which is extended on every page of the app. It seems very convoluted and over-complicated for something so simple. The PHP here is generating numerous issues, although it works initially, which makes this ridiculously difficult to debug. All of the errors returned revolve around the accesstoken - sometimes "doesn't exist', other times 'is invalid', and a host of similar errors.
I'm really struggling to move on from this so would appreciate any help to make the following simpler. It is called on every page, except the home page and the login page:
function handle_facebook()
{
$this->fb = new Facebook\Facebook([
'app_id' => $this->data['app']->fb_app_id,
'app_secret' => $this->data['app']->fb_app_secret,
'default_graph_version' => 'v2.2',
]);
$helper = $this->fb->getCanvasHelper();
$jsHelper = $this->fb->getJavaScriptHelper();
if(!empty($_SESSION['accessToken'])){
$accessToken = $_SESSION['accessToken'];
} else {
try {
$accessToken = $jsHelper->getAccessToken();
} catch(Facebook\Exceptions\FacebookResponseException $e) {
// When Graph returns an error
echo 'Graph returned an error: ' . $e->getMessage();
} catch(Facebook\Exceptions\FacebookSDKException $e) {
// When validation fails or other local issues
echo 'Facebook SDK returned an error: ' . $e->getMessage();
}
if (! isset($accessToken)) {
echo 'No OAuth data could be obtained from the signed request. User has not authorized your app yet.';
} else {
$_SESSION['accessToken'] = $accessToken;
}
}
try {
// Returns a `Facebook\FacebookResponse` object
$response = $this->fb->get('/me?fields=id,name', $accessToken);
$this->user = $response->getGraphUser();
} catch(Facebook\Exceptions\FacebookResponseException $e) {
echo 'Graph returned an error: ' . $e->getMessage();
} catch(Facebook\Exceptions\FacebookSDKException $e) {
echo 'Facebook SDK returned an error: ' . $e->getMessage();
}
return true;
}
To me, it feels as though all I really need is a standalone JS file that can be put on every page that requires a login, that checks logs the user is still logged in, and prompts for a login if they are not. Additionally, I feel like accessing the SDK from PHP should be equally as simple, by getting the accessToken from the JS Canvas helper and storing it in a session, assuming one doesn't already exist.
Where am I going wrong? I feel like I'm missing a step, or something obvious to someone more familiar with Canvas app auth.

Error when sending message with realtime.co - Boolean is not a function

I am using realtime.co for realtime messaging in my .NET 4.5/Javascript webapp.
I created a connection using the code:
xRTML.ready(function () {
xRTML.Config.debug = true;
globalRealtimeConnectionId = generateUUID();
globalRealtimeToken = getRealtimeToken();
globalMyConnection = xRTML.ConnectionManager.create(
{
id: globalRealtimeConnectionId,
appkey: 'xxxx',
authToken: globalRealtimeToken, // insert token
url: 'http://ortc-developers.realtime.co/server/2.1'
});
globalMyConnection.bind(
{
// When we get a message, process it
message: function (e) {
var user = e.message; // the use that just joined
}
});
globalMyConnection.active = true;
});
On the server I gave permissions to "main:*" (all sub channels) and returned a token.
When I send a message to the user from the server using the following code:
OrtcClient client = (OrtcClient)Application["realtime"]; // get reference to client, which initialized in global.asax
client.Send(channel, user); // send message
user is a string with the username, channel is the channel name (e.g. main:12_323_34_. I get the following error in xrtml-custom-3.2.0-min.js:1
Uncaught TypeError: boolean is not a function xrtml-custom-3.2.0-min.js:1
c.Connection.process
(anonymous function)
f.proxy
IbtRealTimeSJ.d.sockjs.d.sockjs.onmessage
x.dispatchEvent
m._dispatchMessage
m._didMessage
m.websocket.d.ws.onmessage
From what I can tell, the client is subscribed because it triggers something when a message is sent to it from the server. But I can't understand the source of the error. Because of the error, the function binded to "message:" is not triggered.
For debugging purposes, if I were you, I would include the non-minified version so you can see exactly what function is causing the error. Once you've done that, it should be easier to track down.
One other quick note is when making RESTful calls you should try to do this in the back end so your api key is not exposed to the public. This would obviously only be an issue when you are creating a public facing website, so if this is an organizational (internal) application you can disregard.

Categories