Avoid exposing LinkedIn API key in HTML - javascript

I want my users to login using the Sign In With LinkedIn feature. The LinkedIn API docs provides the following example snippet for getting started:
<script type="text/javascript" src="//platform.linkedin.com/in.js">
api_key: YOUR_API_KEY_HERE
authorize: true
onLoad: onLinkedInLoad
</script>
<script type="text/javascript">
// Setup an event listener to make an API call once auth is complete
function onLinkedInLoad() {
IN.Event.on(IN, "auth", getProfileData);
}
// Handle the successful return from the API call
function onSuccess(data) {
console.log(data);
}
// Handle an error response from the API call
function onError(error) {
console.log(error);
}
// Use the API call wrapper to request the member's basic profile data
function getProfileData() {
IN.API.Raw("/people/~").result(onSuccess).error(onError);
}
</script>
How to I implement this without exposing YOUR_API_KEY_HERE to the public? There's a few npm packages out there that handle this kind of thing, but they are all old (I get nervous whenever a package hasn't been updated in at least a year).
My application uses node and express. Should I go with an old npm package or is there a better way to hide the api_key?

It is ok and necessary to have YOUR_API_KEY_HERE in the javascript or website and it is necessary at times. They important piece is not to share your SECRET_KEY because you need both to do anything with the API. Be sure to always use HTTPS for all communications.
From the linkedin best practices for security application website:
https://developer.linkedin.com/docs/best-practices
API Key & Secret Key
When making calls to the LinkedIn APIs you use two pieces of identifiable information: the API Key (sometimes called the Consumer Key) and the Secret Key (or Consumer Secret).
The API Key is a public identifier of your application and the Secret Key is confidential and should only be used to authenticate your application on the LinkedIn APIs.
Since both the API Key and Secret Key are needed together to confirm your application’s identity, it is critical that you never expose your Secret Key. Here are some suggestions for proper Secret Key storage:
When creating a native mobile application, do not store it locally on a mobile device.
Do not expose in any client side code files like JavaScript or HTML files.
Do not store it in files on a web server that can be viewed externally e.g.: configuration files, include files, etc.
Do not store it in log files or error messages.
Do not email it or post it on a message board or other public forum.
Remember that when exchanging an OAuth 2.0 authorization code for an access token, the Secret Key is passed as part of the request. Do not expose this request publicly!

Related

Single flow: sign user in via Google oAuth AND grant offline/server access?

I'm trying to implement Google sign-in and API access for a web app with a Node.js back end. Google's docs provide two options using a combo of platform.js client-side and google-auth-library server-side:
Google Sign-In with back-end auth, via which users can log into my app using their Google account. (auth2.signIn() on the client and verifyIdToken() on the server.)
Google Sign-in for server-side apps, via which I can authorize the server to connect to Google directly on behalf of my users. (auth2.grantOfflineAccess() on the client, which returns a code I can pass to getToken() on the server.)
I need both: I want to authenticate users via Google sign-in; and, I want to set up server auth so it can also work on behalf of the user.
I can't figure out how to do this with a single authentication flow. The closest I can get is to do the two in sequence: authenticate the user first with signIn(), and then (as needed), do a second pass via grantOfflineAccess(). This is problematic:
The user now has to go through two authentications back to back, which is awkward and makes it look like there's something broken with my app.
In order to avoid running afoul of popup blockers, I can't give them those two flows on top of each other; I have to do the first authentication, then supply a button to start the second authentication. This is super-awkward because now I have to explain why the first one wasn't enough.
Ideally there's some variant of signIn() that adds the offline access into the initial authentication flow and returns the code along with the usual tokens, but I'm not seeing anything. Help?
(Edit: Some advice I received elsewhere is to implement only flow #2, then use a secure cookie store some sort of user identifier that I check against the user account with each request. I can see that this would work functionally, but it basically means I'm rolling my own login system, which would seem to increase the chance I introduce bugs in a critical system.)
To add an API to an existing Google Sign-In integration the best option is to implement incremental authorization. For this, you need to use both google-auth-library and googleapis, so that users can have this workflow:
Authenticate with Google Sign-In.
Authorize your application to use their information to integrate it with a Google API. For instance, Google Calendar. 
For this, your client-side JavaScript for authentication might require some changes to request
offline access:
$('#signinButton').click(function() {
auth2.grantOfflineAccess().then(signInCallback);
});
In the response, you will have a JSON object with an authorization code:
{"code":"4/yU4cQZTMnnMtetyFcIWNItG32eKxxxgXXX-Z4yyJJJo.4qHskT-UtugceFc0ZRONyF4z7U4UmAI"}
After this, you can use the one-time code to exchange it for an access token and refresh token.
Here are some workflow details:
The code is your one-time code that your server can exchange for its own access token and refresh token. You can only obtain a refresh token after the user has been presented an authorization dialog requesting offline access. If you've specified the select-account prompt in the OfflineAccessOptions [...], you must store the refresh token that you retrieve for later use because subsequent exchanges will return null for the refresh token
Therefore, you should use google-auth-library to complete this workflow in the back-end. For this,
you'll use the authentication code to get a refresh token. However, as this is an offline workflow,
you also need to verify the integrity of the provided code as the documentation explains:
If you use Google Sign-In with an app or site that communicates with a backend server, you might need to identify the currently signed-in user on the server. To do so securely, after a user successfully signs in, send the user's ID token to your server using HTTPS. Then, on the server, verify the integrity of the ID token and use the user information contained in the token
The final function to get the refresh token that you should persist in your database might look like
this:
const { OAuth2Client } = require('google-auth-library');
/**
* Create a new OAuth2Client, and go through the OAuth2 content
* workflow. Return the refresh token.
*/
function getRefreshToken(code, scope) {
return new Promise((resolve, reject) => {
// Create an oAuth client to authorize the API call. Secrets should be
// downloaded from the Google Developers Console.
const oAuth2Client = new OAuth2Client(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
// Generate the url that will be used for the consent dialog.
await oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope,
});
// Verify the integrity of the idToken through the authentication
// code and use the user information contained in the token
const { tokens } = await client.getToken(code);
const ticket = await client.verifyIdToken({
idToken: tokens.id_token!,
audience: keys.web.client_secret,
});
idInfo = ticket.getPayload();
return tokens.refresh_token;
})
}
At this point, we've refactored the authentication workflow to support Google APIs. However, you haven't asked the user to authorize it yet. Since you also need to grant offline access, you should request additional permissions through your client-side application. Keep in mind that you already need an active session.
const googleOauth = gapi.auth2.getAuthInstance();
const newScope = "https://www.googleapis.com/auth/calendar"
googleOauth = auth2.currentUser.get();
googleOauth.grantOfflineAccess({ scope: newScope }).then(
function(success){
console.log(JSON.stringify({ message: "success", value: success }));
},
function(fail){
alert(JSON.stringify({message: "fail", value: fail}));
});
You're done with the front-end changes and you're only missing one step. To create a Google API's client in the back-end with the googleapis library, you need to use the refresh token from the previous step.
For a complete workflow with a Node.js back-end, you might find my gist helpful.
While authentication (sign in), you need to add "offline" access type (by default online) , so you will get a refresh token which you can use to get access token later without further user consent/authentication. You don't need to grant offline later, but only during signing in by adding the offline access_type. I don't know about platform.js but used "passport" npm module . I have also used "googleapis" npm module/library, this is official by Google.
https://developers.google.com/identity/protocols/oauth2/web-server
https://github.com/googleapis/google-api-nodejs-client
Check this:
https://github.com/googleapis/google-api-nodejs-client#generating-an-authentication-url
EDIT: You have a server side & you need to work on behalf of the user. You also want to use Google for signing in. You just need #2 Google Sign-in for server-side apps , why are you considering both #1 & #2 options.
I can think of #2 as the proper way based on your requirements. If you just want to signin, use basic scope such as email & profile (openid connect) to identify the user. And if you want user delegated permission (such as you want to automatically create an event in users calendar), just add the offline access_type during sign in. You can use only signing in for registered users & offline_access for new users.
Above is a single authentication flow.

How long to crack HMAC sha256 digest private key given data

We've built a javascript module which can be embedded in third-party webpages. When this client module is initialized, it executes a transaction within our web application via a cross-site request.
The transaction consists of an external uuid, an email, and some additional meta properties. This message is signed with an HMAC sha256 digest, using our partner's private API key.
Ruby example:
data = {
uuid: "ABCAFAFDS",
email: "email#gmail.com",
meta: {}
}
private_key = "Qd9fe0y2ezqfae4Qj6At"
signature = OpenSSL::HMAC.hexdigest(
OpenSSL::Digest.new("sha256"),
private_key,
data.to_json
)
Within the third-party webpage, the javascript client is then initialized with the signature and the data:
new Client(signature, data).execute();
Initially, our plan was to allow the client to create a partial / incomplete transaction within our system and then require a subsequent back-end request via our REST API to confirm / finalize the transaction. Assuming that we can secure the front-end, however, it would be preferential to remove the back-end confirmation requirement.
Can we reasonably secure the client code using signed messages in this fashion? If the data and the signed message is available in the client, how difficult would it be for a bad actor to brute force the API private key length, given the length above?
most internet traffic has signed tokens on the client these days. All your gmail logins, facebook logins, etc do this so it is fairly standard. I'd recommend using an existing standard (and 3rd party library) rather than roll your own token though. This will let you leverage other people's expertise in this area.
JWT (json web token) is in common use and there are many libraries for working with JWT's. See https://jwt.io for more information.

How to make sure that only a specific domain can query from your REST api?

I have an app that has a REST api. I want it so that the only requests that can be made to the REST api are ones originating from the app itself. How can I do that?
I am using a node.js+express server too.
EDIT: the app is fully a public web app.
Simply define the header in your request, what this does is, it allows requests only from a certain domain, and instantly rejects any other domain.
response.set('Access-Control-Allow-Origin', 'domain.tld');
EDIT: IF you're really keen against web scraping stuff, you could make a function to double check client's origin.
function checkOrigin (origin) {
if (origin === "your.domain.tld") {
return true;
} else {
return false;
}
}
/* Handling it in response */
if (checkOrigin(response.headers.origin)) {
// Let client get the thing from API
} else {
response.write("Send them error that they're not allowed to use the API");
response.end();
}
Above example should work for the default HTTP/HTTPS module, and should also work for Express, if I'm not mistaken.
EDIT 2: To back my claim up that it should also work for Express, I found this quotation at their documentation;
The req (request) and res (response) are the exact same objects that Node provides, so you can invoke req.pipe(), req.on('data', callback), and anything else you would do without Express involved.
I would recommend using an API key from the client. CORS filters are too easy to circumvent.
A simple approach for securing a How to implement a secure REST API with node.js
Overview from above post:
Because users can CREATE resources (aka POST/PUT actions) you need to secure your api. You can use oauth or you can build your own solution but keep in mind that all the solutions can be broken if the password it's really easy to discover. The basic idea is to authenticate users using the username, password and a token, aka the apitoken. This apitoken can be generated using node-uuid and the password can be hashed using pbkdf2
Then, you need to save the session somewhere. If you save it in memory in a plain object, if you kill the server and reboot it again the session will be destroyed. Also, this is not scalable. If you use haproxy to load balance between machines or if you simply use workers, this session state will be stored in a single process so if the same user is redirected to another process/machine it will need to authenticate again. Therefore you need to store the session in a common place. This is typically done using redis.
When the user is authenticated (username+password+apitoken) generate another token for the session, aka accesstoken. Again, with node-uuid. Send to the user the accesstoken and the userid. The userid (key) and the accesstoken (value) are stored in redis with and expire time, e.g. 1h.
Now, every time the user does any operation using the rest api it will need to send the userid and the accesstoken.

Google Plus API - Keyinvalid

I am trying to use the javascript sdk to do an oauth login and access the google plus api. Basically the same code here: https://developers.google.com/api-client-library/javascript/features/authentication
In my firebug console, this is the url that is sending the api request to:
https://content.googleapis.com/discovery/v1/apis/plus/v1/rest?fields=servicePath%2Cresources%2Cparameters%2Cmethods&pp=0&key={key}
This is the error that comes back:
{"error":{"errors":[{"domain":"usageLimits","reason":"keyInvalid","message":"Bad Request"}],"code":400,"message":"Bad Request"}}
I have:
1. Added Google Plus Api to my project
2. Created oauth credentials
3. Setup my consent screen
However, I am still getting the error.
The reason is that you have the key defined in the request. As specified in the discovery API docs (https://developers.google.com/discovery/v1/getting_started#before_starting):
"The APIs Discovery Service provides only public methods that do not
require authentication. In addition, unlike the requests you make to
many other Google APIs, the requests you make to the Discovery Service
API should not include an API key. If you do provide a key, the
requests will fail. This behavior helps ensure that you don't
accidentally disclose your API key when distributing tools that are
based on the Google APIs Discovery Service."
So you can solve the problem by removing the key from your request entirely.
If you are using Google's javascript client to do this and the error occurs when loading further APIs, you have to unset the key first:
gapi.client.setApiKey( null );
gapi.client.load( "plus", "v1", function( apiresponse ) { ... } );
If another function requires the key later, you have to set it again.
To avoid setting and unsetting the key constantly, I load all the needed APIs before authentication, then set the API key and thus will no longer have the issue.

passing javascript to python in GAE

I am planning to run an app in GAE that initializes the Facebook login sequence in Javascript and passes the access token to a Python script to do server-side work. The javascript looks something like this
// Additional init code here
FB.login(
function(response) {
if (response.authResponse) {
var access_token = FB.getAuthResponse()['accessToken'];
console.log('Access Token = '+ access_token);
What would be the easiest way to pass access_token to a Python class (say named fbProcessor) that is imported in main.py?
Since the javascript is executed on the front end (client's browser) you can't really "pass" the access_token to python. You COULD make a http (AJAX) request from javascript to your python app with the access_token.
I think that if you are making a request to python you could just use facebook's python SDK to retrieve the information (including access token) for the current FB authenticated user... IDK the benefits of either way
the python facebook SDK has google app engine example's
If you are using the module within a web application with the
JavaScript SDK, you can also use the module to use Facebook for login,
parsing the cookie set by the JavaScript SDK for logged in users. For
example, in Google AppEngine, you could get the profile of the logged
in user with:
user = facebook.get_user_from_cookie(self.request.cookies, key, secret)
if user:
graph = facebook.GraphAPI(user["access_token"])
profile = graph.get_object("me")
friends = graph.get_connections("me", "friends")
You can see a full AppEngine example application in
examples/appengine.

Categories

Resources