How to Create a Simple Authorization Endpoint Inside SFMC
Published on 11/01/2020
Please note: There is no implied guarantee of security or any recommendations on security level or protection for your tools or websites in this article. This article is intended as educational only and all content contained is not intended as a 'final product' of any kind.
Have you ever run into a need for some added security for your web apps, dashboards, custom endpoints, etc. but don't think you have the tools to build anything? Inside of SFMC there are a couple existing options such as the authenticated user via your SFMC account, using REST packages/components, etc. But each of these can get unweildy, expensive or potentially provide access/permissions outside the scope you want to provide. What if there were a different solution to provide a time-bomb guid for idetnification?
My solution is a custom built endpoint to pass back a token based on a username and password. This token would have an expiration period set to it in order to increase security due to requiring user/pass re-entry for extended use. Please do note that this is only a sample and is very simple in design to provide a baseline, and is not recommended to use 'as is'. The idea is to provide a sample for you and your team to build on and make your own robust security endpoint.
First things first, lets lay the foundation for the endpoint to work. I would recommend the below for your endpoint:
I would highly recommend hosting this as well as all your other endpoints into a single separate Business Unit in your account. This will reduce the access that others will have (increasing security) and it helps to keep it in a centralized location. The second benefit is that this BU can host all your other custom endpoints as well. Since the majority will likely be utilizing REST API, you can host them all in the same enivornment since it would be the SFMC OAuth token that would place the context around the call - not the hosting environment.
It opens the gate for you to store confidential information like REST API ID/Secret codes, User/Pass, and more in a more secure location. That being said, you may want to have added security with Hashing, etc. to decrease the likelihood of someone being able to grab all the info here.
This data extension will be the repository of your users to allow for reference in creating the token. For the sake of this example, I am using it without any encryption or encoding, etc. Please feel free to adjust this to whatever security standards you desire.
Here is my example setup for this DE:
DE Name: Auth_UserPass
Name | Data Type | Length | Primary Key | Nullable | Default Value |
UserName | Text | 254 | Y | N | |
Password | Text | 254 | N | N | |
LastLoginDate | Date | | N | Y | Current Date |
Status | Boolean | | N | Y | False |
UserName: This is the username of the user.
Password: This is the associated password of the user
LastLoginDate: This is used to show the last time a user logged in. I use this to check user activity and validate token matching, etc.
Status: This field allows you to toggle if a user is 'Authorized' or 'Not Authorized' without having to re-enter or delete the user from the Data Extension.
There are plenty of more fields and data points that you can store in here. I just added in my base recommendations above.
This data extension will hold the currently active hash tokens to be referenced for authentication. Again this is just a basic example and you can add/adjust to suit your needs.
Here is my example setup for this DE:
DE Name: Auth_HashToken
Name | Data Type | Length | Primary Key | Nullable | Default Value |
HashAuth | Text | 500 | Y | N | |
UserName | Text | 254 | N | N | |
CreationDate | Date | | N | Y | Current Date |
HashAuth: The Token that was created inside of the auth endpoint
UserName The associated UserName to that token
CreationDate The date and time that the token was created.
As the data retention policy in SFMC only goes down to 1 day minimum, you cannot set these tokens to the hours/minutes that you may want. I am including CreationDate to allow for the easy set up of 'time-bomb' functionality. After whatever length of time you want, you can set it so that the token then no longer becomes viable and the user needs to create a new one. This also allows you to have flexibility on each endpoint to have a separate countdown rather than having it create an 'ExpirationDate' field that'hardcodes' the expiration. That being said, I still recommend having a data retention on this DE to keep it clean, efficient and fast.
This is where you will build the actual endpoint itself. This page will use lookups and SSJS functions to create a JSON return with the token included. I would recommend making this a JSON code resource page inside of Cloud Pages.
There are a couple key things to note before I share the code:
GetPostData(): This handy dandy function is undocumented but powerful for SFMC SSJS. By writing Platform.Request.GetPostData()
you are able to retrieve the data that was sent in via a POST to this page. As a note, this will take any JSON Arrays or Objects and turn them into Strings.
API Endpoint: This page is going to be an API Endpoint, so it will not be rendering or processing any HTML. So any attempts to make HTML in this page will result in the HTML being written as if it were text, not code. Also note that this page will require a POSTed Object to correctly function, so any views inside of a browser will likely result in either a 500 error or a returned error message from the page.
DEV Environment: I usually like to allow myself a 'Dev' (Development) or testing environment inside most of the endpoints, tools or programs I build to allow for easy programming and verification. To this extent I have allowed a flag at the top to turn on/off the development environment. Inside this final version, there is no need for it, but I like to include it for future usage. It is especially beneficial for 'faking' different requirements or conditions to allow for a streamline testing and to limit the variables in your development.
Debug Environment: This is probably as important if not more important than a 'Dev'environment. Having proper Debugging set up is instrumental to a good troubleshooting or QA process. Similar to my dev environment, I start this with a flag at the top to turn on/off. From there I usually write an inline conditional that if debug is true, then to Write() some data points for easy reference. E.g. debug ? Write('Debug is on!') : '';
.
Below is the SSJS code to check if it is a valid user with the correct password, generate a token and then store it internally and pass it back:
<script runat=server>
Platform.Load("Core","1.1.1");
//---------------------------------------------------------//
//---------------------------------------------------------//
// CUSTOM API ENDPOINT TO AUTHORIZE CUSTOM ENDPOINT USAGE
// REQUIRES:
// - Username
// - Password
// - Auth User/Pass DE
// - Auth Hash/GUID DE
//
//---------------------------------------------------------//
//---------------------------------------------------------//
//set Dev environment for debugging and troubleshooting (1: ON | 0: OFF)
var dev = 0;
var debug = 0;
//Gathers the data POSTed to the endpoint and turns it into a JSON
var postStr = Platform.Request.GetPostData();
var postJSON = Platform.Function.ParseJSON(postStr);
debug ? Write('postStr: ' + postStr + '\n') : '';
debug ? Write('postJSON: ' + Stringify(postJSON)+ '\n') : '';
if(postJSON) {
//Gathers the values of the properties passed in payload
var user = postJSON.userName,
pass = postJSON.password
if(!user) {
Write('{"ERROR": "Missing userName"}')
var fail = 1;
} else if(!pass) {
Write('{"ERROR": "Missing password"}')
var fail = 1;
}
// Error checking to output Error Object if missing
// a required property from the payload
if(!fail) {
//grab authentication info from REST token DE
//this is a simple sample, may require different process/security precautions for you to implement
var luDE = DataExtension.Init('Auth_UserPass');
var lu = luDE.Rows.Lookup(['UserName','Password','Status'], [user,pass,1])
debug ? Write('lu: ' + Stringify(lu) + '\n') : '';
debug ? Write('lu.length: ' + lu.length + '\n') : '';
if(lu.length > 0) {
var token = Platform.Function.TreatAsContent('%' + '%=GUID()=%' + '%');
debug ? Write('token: ' + token + '\n') : '';
var hashDE = 'Auth_HashToken';
var insertToken = Platform.Function.InsertData(hashDE,["HashAuth","UserName"],[token,user]);
} else {
Write('{"ERROR": "Authorization is not available. Please add into the reference data"}')
}
}
} else {
Write('{"ERROR": "Missing payload"}')
}
</script>
An example call to this endpoint would be something like:
POST /{{myAuthurl}}
Host: {{myHostDomain}}
Content-Type: application/json
Body:
{
userName: 'myUserName',
password: 'myPassword'
}
and an example return on success would be:
{
"Token": c1d72dac-7986-4944-b8bd-a5395ce6be8c
}
and an example failure would be:
{
"ERROR": "Authorization is not available. Please add into the reference data"
}
I hope this has helped to inspire you to create some awesome authentication endpoints of your own! I do want to make another final note here though that the above is in no way intended to be a final product nor is there any guaranteed or inherit security implied by utilization. It is provided as an example only and is not intended to be utilized as anything other then such. Please leave some comments or reach out with any thoughts, ideas, or questions!