Ever since Devkit made its first entry into the Mule family, a big variety of OAuth enabled Cloud Connectors were made available. Salesforce, Facebook, Twitter, Dropbox, LinkedIn and Google Apps suite are just some examples of the APIs we’ve connected to using that support.
When we started thinking about the August 2013 release we decided to take it one step forward and make it easier than ever.
So, imagine this integration app:
- Multitenant application
- Polls CSV files from a FTP endpoint
- Creates that contact in Salesforce
Conceptually, such a flow should look like this:
So that doesn’t look that bad! It’s certainly way easier that doing the same thing with custom code on a point to point integration. Let’s however pay some attention to its downlights:
Ever since Mule 3.3.1, Cloud Connectors have provided support for multi tenant applications. This basically means that the same application and the same instance of the connector can serve multiple users at the same time. To do this, the connectors store each user’s OAuth information (access and refresh tokens, token server urls, etc) in the Object Store. Since this information goest to the store, that means that each token needs to be assigned with an id. That also means, that each time you want to execute a protected operation you need to specify the id of the token to be used. This is what the enricher above is all about.
This is actually more complex than it sounds, since it requires a lookup to adapt the user id in your mule app to the user id in the OAuth provider. Confusing right? What I’m saying is that in my Mule application, my user id (and thus my tenant id) is probably email@example.com. But then in salesforce, I might have signed up with firstname.lastname@example.org. So, I need a lookup that can turn the @mulesoft.com user into the @mulesource.org. Once I’ve done that lookup, I can use that value into the protected operations so that the connector can actually retrieve the token.
Let’s look at it on XML:
So as you can see, we have an enricher element that contains an object store connector. The connector retrieves a value which key matches the inbound variable “tenantId”, and then that value is used when creating the contact. But that’s not the end of it! You would still need to populate the lookup when authorizing. The connector will automatically store the token with the @mulesource.org key when authorizing, but then you would be responsible for creating the relationship to the tenant id (@mulesoft.com) value, like this:
Simplicity is better
There’s no better lookup than one you don’t need! To spare you all of the hazel above we changed our OAuth support so that by default all tokens are stored under a key that matches the connector’s config name. So, in the example above the Salesforce connector instance we’re using has the name Salesforce. Thus, the access token obtained upon authorization will be stored under the key “Salesforce”. Also, by default, at the time of using a protected operation, it won’t be mandatory to provide an accessTokenId anymore. By default, the connector will try to fetch an access token which key also matches the connector’s config name. So, the code above gets reduced to:
The authorize flow also gets simplified. We don’t need to set any relationship anymore!
Hey, what about multitenancy? Won’t Object Store entries collide?
Excellent question. If I have 10 users I can’t just store the 10 tokens under the connector’s config name because they would overlap… Or would they? If you’re running on CloudHub and using its customer management feature then no, they won’t overlap. Why? Because each tenant there gets its own separate object store partition which is completely unaccessible for other tenants. So, if you have 10 customers, those 10 tokens are in different partitions of the Object Store and thus the keys don’t overlap with each other.
That’s fair, what happens if I run on premise?
If you’re running on-premise or even if you’re running on CloudHub without using customer management support then you’re fine if your application only supports one user. if you want to support multiple, then you have a potential problem with the tokens being overridden since they will all enter the object store with the same key. So this scenario demands a little extra effort from the user but still you won’t need to make any lookups or set any relationships manually. The connector will do that for you. How? We improved the authorize operation so that you can now specify the id you want to obtained token to have on the Object Store. This is an optional parameter, if you don’t specify it then it will take the config’s name by default. But if you do specify it, then the token key will be forced to whatever value you provided. That means that your authorization flow can now look like this:
So as you can see, the token id got set to whatever tenantId you have. And then you can use the same pattern to actually use it on an operation:
That’s it. You just built multi tenancy into your OAuth protected operations way easier than it used to be.
Poll for the win
Another thing you won’t need anymore is the filter that’s checking if the access token is set. Since this is a polling application, a situation very likely to happen is that you try to poll for a tenant who hasn’t yet executed the authorization flow. This will result in an exception saying that the tenant is not yet authorized. However, this is not necessarily an error. It might just be that the user hasn’t finished his on-boarding process or maybe he just had to run to the bathroom! So, you don’t really want your logs filling up with exceptions, that’s why there’s a filter in the original flow. We’re now going to be doing this for you too. We added a new config option on OAuth connectors called “On No Token”. This is an optional attribute that let’s you specify what the connector should do if no token can be found. By default, it will throw the same exception that you would see with an older version of the connector. But, you can also specify an option called “Stop Flow”, which will basically act as a filter and stop the execution of the flow:
And this is how a simpler version of the same app comes to be: