Components that require authentication from the user must implement an authentication module for the service/module they belong to. The authentication module must be named auth.js and must be stored under either the service or module directory (i.e. [vendor]/[service]/auth.js or [vendor/[service]/[module]/auth.js. Appmixer currently supports four types of authentication mechanisms that are common for today's APIs: API key, Password, OAuth 1, and OAuth 2.
Appmixer provides an easy way to configure authentication modules. Most of the time, it's just about configuring a 3rd party service provider URLs for authentication, requesting access tokens, and token validation.
Authentication Module Structure
Each authentication module is a NodeJS module that returns an object with type and definition properties. type can be either apiKey, pwd, oauth (for OAuth 1) and oauth2 (for OAuth 2). definition is either an object or a function (useful in cases where there's a code that you need to run dynamically).
type
The type of authentication mechanism. Any of apiKey, pwd, oauth and oauth2.
definition
The definition of the authentication mechanism is specific to the API service provider. An object or a function. This differs significantly between authentication mechanisms.
If the definition property is specified as a function, in that case, it has one argument context. It is an object that contains either consumerKey, username and password, consumerSecret (OAuth 1) or clientId, clientSecret (OAuth 2) and it always contains callbackUrl property. This will be shown later in the examples.
Authentication mechanisms
As was mentioned in the beginning, Appmixer authentication supports four mechanisms: API Key, Password, OAuth 1, and OAuth 2. Each of them has a different definition.
function, object, or a URL string
In the following examples, we will show how a particular property of the definition object can be specified as a function, object, or string. Let's demonstrate that on a requestProfileInfo property, that one is common for all authentication mechanisms.
// or any library you want to perform API requestsconstrequest=require('request-promise');module.exports= {// there will be some other properties based on the authentication mechanism// defined prior requestProfileInfo definition: {// requestProfileInfo can be defined as a function. In this case, you// can do whatever you need in here and return object with user's// profile information (in promise)requestProfileInfo:async context => {// curl https://mydomain.freshdesk.com/api/v2/agents/me \// -u myApiKey:X'returnrequest({ method:'GET', url:`https://${context.domain}.acme.com/api/v2/agents/me`, auth: { user:context.apiKey // 'context' will be explained later }, json:true }); },// or you can specify it as an object. In this case, the object follows// the 'request' (https://www.npmjs.com/package/request) javascript library// options. requestProfileInfo: { method:'GET', url:'https://acme.com/get-some-records/app_id={{appId}}', headers: {'Authorization':'Basic {{apiKey}}'// {{apiKey}} explained later } },// the last way is to specify just the URI. In this case Appmixer// will perform GET request to that URI. requestProfileInfo:'https://acme.com/get-profile-info?apiKey={{apiKey}}'// if you define 'requestAccessToken' property in an Oauth2 authentication// module with a string that contains a URL, then a POST request will be \// sent to that URL. In other words, what will happen, when a property // is defined as a string, depending on the context (Oauth vs ApiKey ...). }}
API key
This is the most basic type of third-party authentication since you only need to provide a sort of ID and a key provided by the app in concern. In order to use this mechanism, type property must be set to apiKey. Here is an example from Freshdesk components:
module.exports= { type:'apiKey', definition: { tokenType:'authentication-token', auth: { domain: { type:'text', name:'Domain', tooltip:'Your Freshdesk subdomain - e.g. if the domain is <i>https://example.freshdesk.com</i> just type <b>example</b> inside this field' }, apiKey: { type:'text', name:'API Key', tooltip:'Log into your Freshdesk account and find <i>Your API Key</i> in Profile settings page.' } }, accountNameFromProfileInfo:'contact.email',requestProfileInfo:async context => {// curl https://mydomain.freshdesk.com/api/v2/agents/me \// -u myApiKey:X'returncontext.httpRequest({ method:'GET', url:`https://${context.domain}.freshdesk.com/api/v2/agents/me`, auth: { user:context.apiKey, password:'X' }, json:true }); },validate:async context => {// curl https://mydomain.freshdesk.com/api/v2/agents/me \// -u myApiKey:X'constcredentials=`${context.apiKey}:X`;constencoded= (newBuffer(credentials)).toString('base64');awaitcontext.httpRequest({ method:'GET', url:`https://${context.domain}.freshdesk.com/api/v2/agents/me`, headers: {'Authorization':`Basic ${encoded}` } });// if the request doesn't fail, return true (exception will be captured in caller)returntrue; } }};
Now we explain the fields inside the definition object:
auth (object)
This is basically the definition for the Form that will be displayed to the user to collect the data required by the third-party application. Freshdesk requires the domain name and the API key in order to authenticate Appmixer. So we define two fields representing those items and we define the label and tooltip that will appear in the form for each field. In this case, the auth definition will make Appmixer render a form like this:
The values introduced by the user will be exposed in the context object with the same keys as in the auth object. In this case, we will be able to access the values as context.domain and context.apiKey.
While this field is optional, is recommended for a better UX. This field can be a function that returns a promise or object. It is about calling an endpoint from the third-party app which returns the current user info. This will be used to display a name for this account in Appmixer. It is used together with the accountNameFromProfileInfo that will pick one property from this object.
This field is the path in the object returned by requestProfileInfo that points to the value that will be used as the account name. Following the example, the object returned by requestProfileInfo would have a structure like this:
{ contact: { email:'appmixer@example.com', name:'Appmixer example',// More properties here... }// There can be more properties here as well...}
We want to use the email to identify the Freshdesk accounts in Appmixer, so we set accountNameFromProfileInfo as contact.email.
If requestProfileInfo is not defined, the auth object will be used instead. The account name will be the resolved value for the property specified by accountNameFromProfileInfo.
Similar to requestProfileInfo. This is used to validate if the authentication data entered by the user is correct. For this purpose you can call any endpoint that requires authentication, you can even use the same endpoint as requestProfileInfo. If the data is correct, this function should resolve to any non-false value. Otherwise, throw an error or resolve to false. You can define validate as an object. In that case, that object has the same structure as the object passed into the axios library. In that case, it will look like this:
'use strict';module.exports= { type:'apiKey', definition: { tokenType:'apiKey', accountNameFromProfileInfo:'appId', auth: { appId: { type:'text', name:' APP ID', tooltip:'Log into your account and find Api key.' }, apiKey: { type:'text', name:'REST API Key', tooltip:'Found directly next to your App ID.' }, cert: {// just to show, that a 'textarea' input is supported as well type:'textarea', name:'TLS CA', tooltip:'Paste text content of <code>.crt</code> file' } },// In the validate request we need the appId and apiKey specified by the user.// All properties defined in the previous 'auth' object will be// available in the validate call. Just use {{whatever-key-from-auth-object}}// anywhere in the next object. Appmixer will replace these with the// correct values. validate: { method:'GET', url:'https://acme.com/get-some-records/app_id={{appId}}', headers: {'Authorization':'Basic {{apiKey}}' } } }};
If the validate function throws an exception and the exception contains message property, the message will be shown in the Connecting Account Failed page.
The validate can be specified as an object and the Appmixer engine will perform such a request. In this case, the response error can vary from API to API. Appmixer will try to find the error message in the response. If the error message is not shown on the Connecting Account Failed page, then the validate response can always be parsed and a proper exception thrown in the auth.js module using the validateErrCallback function:
validate: {'method': 'GET','uri': 'https://api.apify.com/v2/users/{{apiUserId}}?token={{apiToken}}'}, validateErrCallback: err => {if (err?.someNestedObject?.message) {thrownewError(err.someNestedObject.message); // message for the UI }throw err;}
Password
The password-based authentication is almost similar to key-based authentication as explained in the above section. The only difference is that you will use username and password inputs. The auth property inside definition will need to have two inputs as shown in the below-given code snippet. The validate method is used to validate the input values provided. You can make API call by using the context.username and context.password properties. If the API you are validating returns a token, it will have to be returned from the validate method as shown below.
'use strict';module.exports= { type:'pwd', definition: {// As opposed to the 'apiKey' module, the 'auth' property inside the // definition object is optional for the 'pwd' type. If omitted, it will // have the following structure. If you want to change name of the field,// change the content of the tooltip or add another field, you can do// that here. auth: { username: { type:'text', name:'Username', tooltip:'Username' }, password: { type:'password', name:'Password', tooltip:'Password' } },// the only mandatory property for the 'pwd' authentication typevalidate:async context => {const { username,password } = contextconst { data } =awaitcontext.httpRequest.post(// the URL for the username/password authentication'https://service-server-api-url/auth', { username, password } );// verify authentication worksif (!data.isValid) {thrownewError("Invalid username/password combination."); }// if the API does not return a token for exchange, then return here.// in such case, the components using this authentication module// will have to use the username/password to perform each request.// There will be properties context.auth.username and // context.auth.password available in the context object in a component.// if the API returns a token and expiration then return them using // the following way - object with 'token' and 'expires' properties.const { token,expiresIn } = data;// the token and expiration (if provided) will be available in a// component's context object under context.auth.token and// context.auth.expiresreturn { token,// expires can be a Date - token expiration date (timestamp),// or number/string with seconds representing a token lifetime.// Appmixer will use this to request a new token (using stored// username and password) when the current one is about to expire expires: expiresIn };// if the API returns a token that does not expire, just return// that token // return { token }; } }};
HTTP Basic Authentication
The pwd type can be used for the HTTP Basic Authentication. Here's a sample code:
In order to use this mechanism, type property must be set to oauth. Here is an example from Trello components:
'use strict';constOAuth=require('oauth').OAuth;constPromise=require('bluebird');module.exports= { type:'oauth',// In this example, 'definition' property is defined as a function. definition: context => {let trelloOauth =Promise.promisifyAll(newOAuth('https://trello.com/1/OAuthGetRequestToken','https://trello.com/1/OAuthGetAccessToken',context.consumerKey,context.consumerSecret,'1.0',context.callbackUrl,'HMAC-SHA1' ), { multiArgs:true });return { accountNameFromProfileInfo:'id',authUrl: context => {return'https://trello.com/1/OAuthAuthorizeToken'+'?oauth_token='+context.requestToken +'&name=AppMixer'+'&scope=read,write,account'+'&expiration=never'; },requestRequestToken: () => {returntrelloOauth.getOAuthRequestTokenAsync().then(result => {return { requestToken: result[0], requestTokenSecret: result[1] }; }); },requestAccessToken: context => {returntrelloOauth.getOAuthAccessTokenAsync(context.requestToken,context.requestTokenSecret,context.oauthVerifier ).then(result => {return { accessToken: result[0], accessTokenSecret: result[1] }; }); },requestProfileInfo: context => {returntrelloOauth.getProtectedResourceAsync('https://api.trello.com/1/members/me','GET',context.accessToken,context.accessTokenSecret ).then(result => {if (result[1].statusCode !==200) {thrownewError(result[1].statusMessage); } result =JSON.parse(result[0]);// get rid of limits for now// may and will contain keys with dots - mongo doesn't like itdeleteresult.limits;return result; }); },validateAccessToken: context => {returntrelloOauth.getProtectedResourceAsync('https://api.trello.com/1/tokens/'+context.accessToken,'GET',context.accessToken,context.accessTokenSecret ).then(result => {if (result[1].statusCode ===401) {thrownewcontext.InvalidTokenError(result[1].statusMessage); }if (result[1].statusCode !==200) {thrownewError(result[1].statusMessage); } result =JSON.parse(result[0]);if (result['dateExpires'] ===null) {return; }thrownewcontext.InvalidTokenError('Invalid token.'); }); } }; }};
Note that in this case, the definition is a function instead of an object, but it still returns an object with the items needed for OAuth 1 authentication, similar to API key authentication. Now we explain the fields from the definition object:
This must be a function (or an object, or just a string URL as explained here) that returns a promise which must resolve to an object containing requestToken and requestTokenSecret the same way that is shown in the example. Those are needed to get the access token and become exposed by the context - context.requestToken and context.requestTokenSecret.
This must be a function (or an object, or just a string URL as explained here) that returns a promise which must resolve to an object containing accessToken and accessTokenSecret the same way that is shown in the example. Usually, you will be using the requestToken and requestTokenSecret inside this function, as they are required by the OAuth 1 flow in this step. Similarly to requestRequestToken function, accessToken and accessTokenSecret will become exposed by the context - context.accessToken and context.accessTokenSecret.
Function, object or a string URL returning auth URL. Appmixer will then use this URL to redirect the user to the proper login page. The requestToken is available in the context. The example shows the authUrl declaration using the token provided by the context.
This property serves the same purpose as validate property in the API Key mechanism. This is used by Appmixer to test if the access token is valid and accepted by the third-party app. You have access to context.accessToken and context.accessTokenSecret to make authenticated requests. If the token is valid, this function should resolve to any non-false value. Otherwise, throw an error or resolve to false.
OAuth 2
The latest OAuth protocol and industry-standard, OAuth 2.0 improved many things from the first version in terms of usability and implementation, while maintaining a high degree of security. It is easier to implement in Appmixer as well. In order to use this mechanism, type property must be set to oauth2. Here is an example from Asana auth.js:
'use strict';constrequest=require('request-promise');module.exports= { type:'oauth2',// function definition is used in this case because of the 'profileInfo'// property. It is set in the 'requestAccessToken' function and then// later returned in 'requestProfileInfo' function.definition: () => {let profileInfo;return {accountNameFromProfileInfo: context => {returncontext.profileInfo['email'] ||context.profileInfo['id'].toString(); }, authUrl:'https://app.asana.com/-/oauth_authorize',requestAccessToken: context => {// don't put params into post body, won't work, have to be in querylet tokenUrl ='https://app.asana.com/-/oauth_token?'+'grant_type=authorization_code&code='+context.authorizationCode +'&redirect_uri='+context.callbackUrl +'&client_id='+context.clientId +'&client_secret='+context.clientSecret;returnrequest({ method:'POST', url: tokenUrl, json:true }).then(result => { profileInfo = result['data'];let newDate =newDate();newDate.setTime(newDate.getTime() + (result['expires_in'] *1000));return { accessToken: result['access_token'], refreshToken: result['refresh_token'], accessTokenExpDate: newDate }; }); },requestProfileInfo: () => {return profileInfo; },refreshAccessToken: context => {// don't put params into post body, won't work, have to be in querylet tokenUrl ='https://app.asana.com/-/oauth_token?'+'grant_type=refresh_token&refresh_token='+context.refreshToken +'&redirect_uri='+context.callbackUrl +'&client_id='+context.clientId +'&client_secret='+context.clientSecret;returnrequest({ method:'POST', url: tokenUrl, json:true }).then(result => { profileInfo = result['data'];let newDate =newDate();newDate.setTime(newDate.getTime() + (result['expires_in'] *1000));return { accessToken: result['access_token'], accessTokenExpDate: newDate,// Some services return a new refresh token, if so, it can// be returned here and Appmixer will replace the old one. refreshToken: result['refresh_token'] }; }); }, validateAccessToken: { method:'GET', url:'https://app.asana.com/api/1.0/users/me', auth: { bearer:'{{accessToken}}' } } }; }};
Notice that there is no requestRequestToken method in OAuth 2, but there is the requestAccessToken used to get the token and refreshAccessToken method, which is used by Appmixer to refresh the access tokens. Now we explain the definition object's properties:
Similar to OAuth 1, we should provide the authentication URL for the third-party app. However, due to the different authentication flows supported by OAuth 2, the way this is defined may vary according to the third-party implementation. If the OAuth 2 implementation is standard, you can define the authUrl with just a string like:
Standard means, there is a response_typeparameter set to code, then there is the client_id, redirect_uri, state and scope parameter. If the OAuth 2 implementation requires any other parameters (or the standard ones use different names), then you have to define this property as a function and provide all the additional parameters. The same logic applies to the following property requestAccessToken.
This function should return a promise with an object which contains accessToken, refreshToken (optional, some OAuth 2 implementations do not have refresh tokens) and accessTokenExpDate or expires_in (also options if the implementation does not have tokens that expire). Inside this function, we call the endpoint which handles the access tokens for the application. Inside this function you have access to context properties you need: clientId, clientSecret, callbackUrl and authorizationCode.
Part of the OAuth 2 specification is the ability to refresh short-lived tokens via a refresh token that is issued along with the access token. This function should call the refresh token endpoint on the third-party app and resolve to an object with accessToken and accessTokenExpDate (and refreshToken if needed) properties, as shown in the example. You have access to context properties clientId, clientSecret, callbackUrl and refreshToken.
It can be that simple, if the third party API implements OAuth 2 according to standards. And in this Dropbox case, their OAuth 2 implementation does not contain refresh tokens.
Sometimes the OAuth 2 needs a scope or a different scope delimiter. Here is a full example of the Microsoft authentication module with such features:
String. The default one is , and you can change it to ' ' for example.
refreshBeforeExp
By default, the Appmixer engine will try to refresh the access token five minutes before its expiration. This is fine for most of the OAuth2 implementations, where an access token is usually valid for hours or days. However, there are OAuth2 implementations with stricter rules. With this property, you can define how many minutes (default, see refreshBeforeExpUnits) before the access token expiration should Appmixer refresh the token. Appmixer will not try to refresh the token before this value.
module.exports= { type:'oauth2',definition: () => {// telling Appmixer it cannot refresh it sooner than 30 seconds // before access token expiration refreshBeforeExp:30, refreshBeforeExpUnits:'seconds',// 'minutes' by default// the rest is business as usual authUrl:'https://www.acme.com/oaut2/authorize'... }}
refreshBeforeExpUnits
Works in cooperation with the refreshBeforeExp property. Useful if you need to go down to seconds. See the example above.
OAuth 2 redirect URI
When you're developing an OAuth 2 application, at some point you have to register an app in the 3rd party system. For that, you need the redirect URI that points to the Appmixer API. The format of the redirect URI is https://[appmixer-url]/auth/[service]/callback.
So if the service you're developing is called myService then the redirect URI will be https://[appmixer-url]/auth/myService/callback.
Context
Context properties are different for each authentication type. But some of them are common for all types.
module.exports= { type:'pwd', definition: {// the only mandatory property for the 'pwd' authentication typevalidate:async context => {const { username,password } = context;const { data } =awaitcontext.httpRequest.post(// the URL for the username/password authentication'https://service-server-api-url/auth', { username, password } );// verify authentication worksif (!data.isValid) {thrownewError("Invalid username/password combination."); } returntrue; } }};
Custom "Connect account" button
There is a possibility to redefine the Connect Account button in the Designer and Integration Wizards.
This is done with an optional connecAccountButton: { image: 'data uri' } property. Example from the Google auth.js:
'use strict';constGoogleApi=require('googleapis');constPromise=require('bluebird');module.exports= { type:'oauth2',definition: initData => {return {// The auth.js 'definition' object can have an optional // 'connectAccountButton' property, which has to contain the 'image'. connectAccountButton: { image: 'data:image/png;base64,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' },
scope: ['profile','email'],accountNameFromProfileInfo:function(context) {returncontext.profileInfo.email; },...}
Setting OAuth 1,2 secrets
The OAuth applications need some secrets: consumerKey, consumerSecret (OAuth 1), or clientId, clientSecret (OAuth 2).
You can use the Backoffice to do that. This is the preferable way.
Another way is the credentials.json file which has to be located where your auth.js file is.
Then when you pack and publish your component archive, Appmixer will read the file and store the values into Mongo DB. Later it will use them to create the context object for your auth.js module.