Salesforce OAuth Node.js Redis and JSON Web Tokens – keeping active sfdc users oauth info in redis for an app with short session
Basically, the difference between these two models is one uses a server as an intermediary for the client app to talk to and in turn talk to salesforce. The other just uses the client app (probably in the browser) to perform the oauth handshake. So what is more secure? I am not completely sure. At first the user agent flow seems most insecure, but if your server is insecure then user agent flow might be better. In any case, I think OAuth is a great standard, but you need to do your due diligence when writing your app to make sure it is secure.
Anyways, I like REST. I think it makes development a bit more fun, and has it’s advantages in performance. So when I first played with getting an authorized user in an app for salesforce, I explored the user agent flow. This makes sense and is simple, but it can frustrate you if you don’t know everything about salesforce.
If you make request to salesforce from the client app using a valid access token from the user agent flow, and that client app is a web app, the requests straight from the users we browser to salesforce might not work as you expect. You will most likely get this error:
No 'Access-Control-Allow-Origin' header is present on the requested resource...
This is a security error salesforce sends because it notices the request is coming from the browser, but the origin or the request is not accepted. The browser will send a header showing the request origin which triggers salesforce to not accept the request. This might frustrate you more if you notice that the token you send from the client web app works if you use a simple curl call from your machine. This is because the curl call is minimal (no browser headers), and the access token is valid.
So what do you do? Skip out on making calls from the client app? That’s the best solution in my opinion, but there is a salesforce setting that will allow the requests to be valid. If you add the domain/origin to Setup -> Security -> CORS the request will work. But this is limiting in the fact that the integration app is specific to the salesforce org.
In most cases, people are implementing custom integrations and business logic specific to their business or Salesforce org, and adding this setting isn’t much of a problem. But if your aim is to make an integration that works with all salesforce orgs, adding a custom CORS setting to each org is not ideal. In comes Web Sever Flow.
So you must make requests from the server. Makes sense, and it will work with most Salesforce org (granted there is no IP restrictions, etc). But as I said before I like REST, mainly so I can build more of an “app” and less of a server passing pages to the user (it has plenty of other advantages as well). So will I have to make a requests from the browser to my web server to Salesforce? Yes, that is the flow, and it’s not a bad thing, it gives you more power to dictate how to store the important things (access tokens and more). You can also build a fun single page app that makes lightweight requests to your server and still accomplishes communications with salesforce in a restful way. Heres a basic look at the flow
OLD POST……..pretty much gibberish
the day after I posted this, I realized this implementation is wacky and somewhat not secure. The danger lies with the URL and using redis for oauth info. Granted, this approach using redis is used a lot in a secure way, especially in servers that use multiple instances of compute power and need a common fast data key/val store. In those cases, the redis server is secured and the there is a bit of encryption needed to keep things safe. In practice I think server memory (sessions) is probably the best place to keep an access token, of course you should encrypt it in memory.
The main problem I see with the implementation below (besides it’s confusing), is that the JWT has the encrypted access token on the URL. This means that if a router or server log that gets the redirect request I send after SFDC provides an access token, it could potentially be visible in the server logs allowing someone a chance at cracking the JWT and getting the access token. This is why we should really store the access token securely in redis/a database, that has a secure connection, and encrypts the values.
I will update this again next week with details on how we could use redis in a more secure fashion to store the access token, and then use is in a scalable app (more compute!)
On rabbit hole some people fall into is they get an error making rest calls to Salesforce.
No 'Access-Control-Allow-Origin' header is present on the requested resource...
This comes from making rest calls from the browser to Salesforce and Salesforce rejecting the call. This is frustrating, especially because that token can be used from my computer in a curl command. By best advice, learn to live with it. A little more advice, security is your friend. It’s always better to be more secure. Even if this requires more code and more understanding of the authentication process.
So if SFDC is saying it’s insecure, let’s dig into the issue. I am going to use node.js for the server in this, but the same concepts can be applied to many frameworks. I am also going to use pseudo code examples (for node.js), so this isn’t a working project. I will also put a link to the demo and github repo of my implementation in node.js below.
Getting back to this issue. Let’s first go over the setup to get an OAuth access token. You will need a connected app in a Salesforce org. It can be any org, where you create the app will not limit you to just that org. The app is merely a token for all of salesforce’s instances url, and you can use the OAuth user authorization flow to get access to orgs separate from where the connected app lives. This was eye opening the first time I learned this, I want to make it clear for everyone just in case. Set the app to use OAuth, give it the proper scopes (refresh_token, api, user, etc), set the callback to localhost:3000/callback (or whatever your callback is), and save the app. You will get a consumer id and consumer secret. These plus the callback url are used are all needed in the server app to connect the user to Salesforce. What happens is your server (node.js) has an /authorize endpoint, or whatever you want to call it. This endpoint simply redirects the request to the salesforce authorization url, which is the login page for the user but the url is slightly different so it know it is associated from your connected app. Your server also has a /callback endpoint, which is the one you specified in your connected app. After the user logs in, the request goes to /callback, where you now have an access token to use from the server.
This is great, now we have an access token. But we can only use it from the server. So what do I do in the /callback route? I get the access token from salesforce then what? I have a rest app I am serving statically and I want to pass this access token to so when it call my server I can then talk to salesforce. Okay that sounds okay, I will send it back via url params, so then the client app can make ajax calls to an api on my node server that will check for the access token on the url. Okay, that works. Seems hacky… It is hacky, and it is wrong. I should not expose this token. Although Salesforce probably would deny other requests from clients with that token, it is still not good practice. In theory that Salesforce access token should not leave that server that got access.
So how do we solve this? There are a few ways session, tokens, urls, headers, and probably more I do not know of. The main concept is to keep the access token and other salesforce auth info on the server, generate a hash that represents where to find this data on the server, and give the hash to the client so that subsequent request contain the location to the valid users salesforce auth info. Of course there is a bit of encrypt with the hash’s and mechanisms to secure the data on the server that goes with it, but in general this is the idea. The client can keep this hash in a variety of ways, in a cookie, in the header, in the url. The hash can also be encoded in a variety of ways. The different ways in which the hash is stored with the client and encoded determines if it is a session, or token, or whatever, but in the end they all seem to follow the basic outlining principal above.
Keep in mind I want keep my client web app as restful as possible, so on the server all I do is serve a static folder at a route I want the client to be served. Then I am free to build the client as an app, and not pages coming from a server.
So given this constraint, I chose to use url parameters and a Json Web Token (JWT) to be the mechanism to pass the hash back and forth. In addition, I chose to use redis as a in memory storage unit for the salesforce oauth info. The JWT is an encoded token that has the users Salesforce Id encoded. This is passed to the client via url params, and the client never decodes this, because it can’t. The redis pretty much act as a fast database that doesn’t last forever (think 1 day max). This is used to store the oauth info for each user. Each user is stored with a key of their Salesforce user id, which acts as the hash in this scenario.
So using JWT’s and redis, the flow now goes. After a user logs into Salesforce the request hits /callback on our server. We get and store the user oauth info on redis with the user id as the key to find this. Then we make a JWT with the user id and redirect back to our client with the jwt in the url as a parameter. Since our client is served statically nothing happens with the url param in the route on the server end, the js app loads in the browser, and has access to look at the url in js to control what it renders and get the jwt to make further requests to the node server. The node server might then have a small api that checks for the jwt in each endpoint, if there is one it would decode it to get the user id to find the user oauth info in redis, then call salesforce from the node server. When that finishes it then returns back to the rest call the client made with the data it got from salesforce. To make this more secure one might want to encrypt the oauth info stored in redis as well as add an expiration date to each jwt.
Okay cool, now we can use salesforce oauth redis and jwts all in one small secure app!