28 | )
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/functions/utils/getUserData.js:
--------------------------------------------------------------------------------
1 | import request from 'request'
2 | import querystring from 'querystring'
3 | import { config } from './oauth'
4 |
5 | /* Call into https://app.intercom.io/me and return user data */
6 | export default function getUserData(token) {
7 | const postData = querystring.stringify({
8 | client_id: config.clientId,
9 | client_secret: config.clientSecret,
10 | app_id: config.appId
11 | })
12 |
13 | const requestOptions = {
14 | url: `${config.profilePath}?${postData}`,
15 | json: true,
16 | auth: {
17 | user: token.token.token,
18 | pass: '',
19 | },
20 | headers: {
21 | 'Content-Type': 'application/x-www-form-urlencoded',
22 | 'Accept': 'application/json',
23 | }
24 | }
25 |
26 | return requestWrapper(requestOptions, token)
27 | }
28 |
29 | /* promisify request call */
30 | function requestWrapper(requestOptions, token) {
31 | return new Promise((resolve, reject) => {
32 | request(requestOptions, (err, response, body) => {
33 | if (err) {
34 | return reject(err)
35 | }
36 | // return data
37 | return resolve({
38 | token: token,
39 | data: body,
40 | })
41 | })
42 | })
43 | }
44 |
--------------------------------------------------------------------------------
/functions/utils/oauth.js:
--------------------------------------------------------------------------------
1 | import simpleOauth from 'simple-oauth2'
2 |
3 | const intercomApi = 'https://app.intercom.io'
4 | /* process.env.URL from netlify BUILD environment variables */
5 | const siteUrl = process.env.URL || 'http://localhost:3000'
6 |
7 | export const config = {
8 | /* values set in terminal session or in netlify environment variables */
9 | appId: process.env.INTERCOM_APP_ID,
10 | clientId: process.env.INTERCOM_CLIENT_ID,
11 | clientSecret: process.env.INTERCOM_CLIENT_SECRET,
12 | /* Intercom oauth API endpoints */
13 | tokenHost: intercomApi,
14 | authorizePath: `${intercomApi}/oauth`,
15 | tokenPath: `${intercomApi}/auth/eagle/token`,
16 | profilePath: `${intercomApi}/me/`,
17 | /* redirect_uri is the callback url after successful signin */
18 | redirect_uri: `${siteUrl}/.netlify/functions/auth-callback`,
19 | }
20 |
21 | function authInstance(credentials) {
22 | if (!credentials.client.id) {
23 | throw new Error('MISSING REQUIRED ENV VARS. Please set INTERCOM_CLIENT_ID')
24 | }
25 | if (!credentials.client.secret) {
26 | throw new Error('MISSING REQUIRED ENV VARS. Please set INTERCOM_CLIENT_SECRET')
27 | }
28 | // return oauth instance
29 | return simpleOauth.create(credentials)
30 | }
31 |
32 | /* Create oauth2 instance to use in our two functions */
33 | export default authInstance({
34 | client: {
35 | id: config.clientId,
36 | secret: config.clientSecret
37 | },
38 | auth: {
39 | tokenHost: config.tokenHost,
40 | tokenPath: config.tokenPath,
41 | authorizePath: config.authorizePath
42 | }
43 | })
44 |
--------------------------------------------------------------------------------
/functions/auth-callback.js:
--------------------------------------------------------------------------------
1 | import getUserData from './utils/getUserData'
2 | import oauth2, { config } from './utils/oauth'
3 |
4 | /* Function to handle intercom auth callback */
5 | exports.handler = (event, context, callback) => {
6 | const code = event.queryStringParameters.code
7 | /* state helps mitigate CSRF attacks & Restore the previous state of your app */
8 | const state = event.queryStringParameters.state
9 |
10 | /* Take the grant code and exchange for an accessToken */
11 | oauth2.authorizationCode.getToken({
12 | code: code,
13 | redirect_uri: config.redirect_uri,
14 | client_id: config.clientId,
15 | client_secret: config.clientSecret
16 | })
17 | .then((result) => {
18 | const token = oauth2.accessToken.create(result)
19 | console.log('accessToken', token)
20 | return token
21 | })
22 | // Get more info about intercom user
23 | .then(getUserData)
24 | // Do stuff with user data & token
25 | .then((result) => {
26 | console.log('auth token', result.token)
27 | // Do stuff with user data
28 | console.log('user data', result.data)
29 | // Do other custom stuff
30 | console.log('state', state)
31 | // return results to browser
32 | return callback(null, {
33 | statusCode: 200,
34 | body: JSON.stringify(result)
35 | })
36 | })
37 | .catch((error) => {
38 | console.log('Access Token Error', error.message)
39 | console.log(error)
40 | return callback(null, {
41 | statusCode: error.statusCode || 500,
42 | body: JSON.stringify({
43 | error: error.message,
44 | })
45 | })
46 | })
47 | }
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Netlify + Intercom OAuth
2 |
3 | Add 'login with Intercom' via Netlify Functions & OAuth!
4 |
5 |
6 | - [About the project](#about-the-project)
7 | - [How to install and setup](#how-to-install-and-setup)
8 | - [Running the project locally](#running-the-project-locally)
9 | - [Deploying](#deploying)
10 | - [How it works with Netlify Functions](#how-it-works-with-netlify-functions)
11 | * [auth.js function](#authjs-function)
12 | * [auth-callback.js function](#auth-callbackjs-function)
13 |
14 |
15 | ## About the project
16 |
17 | This project sets up a "login with Intercom" OAuth flow using netlify functions.
18 |
19 | Here is a quick demo of the login flow, and the OAuth Access data you get back:
20 |
21 | 
22 |
23 | You can leverage this project to wire up Intercom (or other OAuth providers) login with your application.
24 |
25 | > TLDR; [Watch the 11 minute video](https://www.youtube.com/watch?v=HuIS6jvK8S8) explaining **everything**
26 |
27 |
28 |
29 | ---
30 |
31 | Let's get started with how to get setup with the repo and with Intercom.
32 |
33 | ## How to install and setup
34 |
35 | 1. **Clone down the repository**
36 |
37 | ```bash
38 | git clone git@github.com:DavidWells/intercom-netlify-oauth.git
39 | ```
40 |
41 | 2. **Install the dependencies**
42 |
43 | ```bash
44 | npm install
45 | ```
46 |
47 | 3. **Create an Intercom OAuth app**
48 |
49 | Lets go ahead and setup the Intercom app we will need!
50 |
51 | [Create an Intercom OAuth app here](https://app.intercom.com/developers/)
52 |
53 | You need to enable a 'test' app in your account. It's a tricky to find but you can create a TEST app in your Intercom account under `Settings > General`
54 |
55 | `https://app.intercom.com/a/apps/your-app-id/settings/general`
56 |
57 | 
58 |
59 | After enabling the test app, you can find it listed in your [intercom developer portal](https://app.intercom.com/developers/).
60 |
61 | We now need to configure the test app.
62 |
63 | Input the live "WEBSITE URL" and "REDIRECT URLS" in the app edit screen.
64 |
65 | 
66 |
67 | You will want to have your live Netlify site URL and `localhost:3000` setup to handle the redirects for local development.
68 |
69 | If you haven't deployed to Netlify yet, just insert a placeholder URL like `http://my-temp-site.com` but **remember to change this once your Netlify site is live with the correct URL**
70 |
71 | Our demo app has these `REDIRECT URLS` values that are comma separated
72 |
73 | ```bash
74 | https://intercom-login-example.netlify.com/.netlify/functions/auth-callback,
75 | http://localhost:3000/.netlify/functions/auth-callback
76 | ```
77 |
78 | Great we are all configured over here.
79 |
80 | 4. **Grab your the required config values**
81 |
82 | We need our Intercom app values to configure our function environment variables.
83 |
84 | Navigate back to the main OAuth screen and grab the **App ID**, **Client ID**, and **Client Secret** values. We will need these to run the app locally and when deploying to Netlify.
85 |
86 | 
87 |
88 | ## Running the project locally
89 |
90 | Because we are using `netlify-lambda` to build & serve functions locally, we can work on this project without needing to redeploy to reflect changes!
91 |
92 | We need to set an Intercom app id and OAuth client id + secret in your terminal environment for the functions to connect to your Intercom app.
93 |
94 | After creating and configuring your [Intercom OAuth app](https://app.intercom.com/developers/), it's time to plugin the required environment variables into your local terminal session.
95 |
96 | On linux/MacOS, run the following command in your terminal:
97 |
98 | ```bash
99 | export INTERCOM_APP_ID=INTERCOM_APP_ID
100 | export INTERCOM_CLIENT_ID=INTERCOM_CLIENT_ID
101 | export INTERCOM_CLIENT_SECRET=INTERCOM_CLIENT_SECRET
102 | ```
103 |
104 | If you are on a window machine, set the environment variable like so:
105 |
106 | ```bash
107 | set INTERCOM_APP_ID=INTERCOM_APP_ID
108 | set INTERCOM_CLIENT_ID=INTERCOM_CLIENT_ID
109 | set INTERCOM_CLIENT_SECRET=INTERCOM_CLIENT_SECRET
110 | ```
111 |
112 | Then run the start command
113 |
114 | ```bash
115 | npm start
116 | ```
117 |
118 | This will boot up our functions to run locally for development. You can now login via your Intercom application and see the token data returned.
119 |
120 | Making edits to the functions in the `/functions` will hot reload the server and you can iterate on building your custom logic.
121 |
122 | ## Deploying
123 |
124 | Use the one click "deploy to Netlify" button to launch this!
125 |
126 | [](https://app.netlify.com/start/deploy?repository=https://github.com/davidwells/intercom-netlify-oauth)
127 |
128 | Alternatively, you can connect this repo with your Netlify account and add in your values.
129 |
130 | In `https://app.netlify.com/sites/YOUR-SITE-SLUG/settings/deploys` add the `INTERCOM_APP_ID`, `INTERCOM_CLIENT_ID`, and `INTERCOM_CLIENT_SECRET` values to the "Build environment variables" section of settings
131 |
132 | 
133 |
134 | After your site is deployed, you should be able to test your Intercom login flow.
135 |
136 | ## How it works with Netlify Functions
137 |
138 | Once again, serverless functions come to the rescue!
139 |
140 | We will be using 2 functions to handle the entire OAuth flow with Intercom.
141 |
142 | **Here is a diagram of what is happening:**
143 |
144 | 
145 |
146 | 1. First the `auth.js` function is triggered & redirects the user to Intercom
147 | 2. The user logs in via Intercom and is redirected back to `auth-callback.js` function with an **auth grant code**
148 | 3. `auth-callback.js` takes the **auth grant code** and calls back into Intercom's API to exchange it for an **AccessToken**
149 | 4. `auth-callback.js` now has the **AccessToken** to make any API calls it would like back into the Intercom App.
150 |
151 | This flow uses the [Authorization Code Grant](https://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.1) flow. For more information on OAuth 2.0, [Watch this video](https://www.youtube.com/watch?v=CPbvxxslDTU)
152 |
153 | Let's dive into the individual functions and how they work.
154 |
155 | ### auth.js function
156 |
157 | The `auth.js` function creates an `authorizationURI` using the [`simple-oauth2` npm module](https://www.npmjs.com/package/simple-oauth2) and redirects the user to the Intercom login screen.
158 |
159 | Inside of the `auth.js` function, we set the `header.Location` in the lambda response and that will redirect the user to the `authorizationURI`, a.k.a the Intercom oauth login screen.
160 |
161 |
162 |
163 | ```js
164 | /* code from /functions/auth.js */
165 | import oauth2, { config } from './utils/oauth'
166 |
167 | /* Do initial auth redirect */
168 | exports.handler = (event, context, callback) => {
169 | /* Generate authorizationURI */
170 | const authorizationURI = oauth2.authorizationCode.authorizeURL({
171 | redirect_uri: config.redirect_uri,
172 | /* Specify how your app needs to access the user’s account. http://bit.ly/intercom-scopes */
173 | scope: '',
174 | /* State helps mitigate CSRF attacks & Restore the previous state of your app */
175 | state: '',
176 | })
177 |
178 | /* Redirect user to authorizationURI */
179 | const response = {
180 | statusCode: 302,
181 | headers: {
182 | Location: authorizationURI,
183 | 'Cache-Control': 'no-cache' // Disable caching of this response
184 | },
185 | body: '' // return body for local dev
186 | }
187 |
188 | return callback(null, response)
189 | }
190 | ```
191 |
192 |
193 | ### auth-callback.js function
194 |
195 | The `auth-callback.js` function handles the authorization grant code returned from the successful Intercom login.
196 |
197 | It then calls `oauth2.authorizationCode.getToken` to get a valid `accessToken` from Intercom.
198 |
199 | Once you have the valid accessToken, you can store it and make authenticated calls on behalf of the user to the Intercom API.
200 |
201 |
202 |
203 | ```js
204 | /* code from /functions/auth-callback.js */
205 | import getUserData from './utils/getUserData'
206 | import oauth2, { config } from './utils/oauth'
207 |
208 | /* Function to handle intercom auth callback */
209 | exports.handler = (event, context, callback) => {
210 | const code = event.queryStringParameters.code
211 | /* state helps mitigate CSRF attacks & Restore the previous state of your app */
212 | const state = event.queryStringParameters.state
213 |
214 | /* Take the grant code and exchange for an accessToken */
215 | oauth2.authorizationCode.getToken({
216 | code: code,
217 | redirect_uri: config.redirect_uri,
218 | client_id: config.clientId,
219 | client_secret: config.clientSecret
220 | })
221 | .then((result) => {
222 | const token = oauth2.accessToken.create(result)
223 | console.log('accessToken', token)
224 | return token
225 | })
226 | // Get more info about intercom user
227 | .then(getUserData)
228 | // Do stuff with user data & token
229 | .then((result) => {
230 | console.log('auth token', result.token)
231 | // Do stuff with user data
232 | console.log('user data', result.data)
233 | // Do other custom stuff
234 | console.log('state', state)
235 | // return results to browser
236 | return callback(null, {
237 | statusCode: 200,
238 | body: JSON.stringify(result)
239 | })
240 | })
241 | .catch((error) => {
242 | console.log('Access Token Error', error.message)
243 | console.log(error)
244 | return callback(null, {
245 | statusCode: error.statusCode || 500,
246 | body: JSON.stringify({
247 | error: error.message,
248 | })
249 | })
250 | })
251 | }
252 | ```
253 |
254 |
255 | Using two simple lambda functions, we can now handle logins via Intercom or any other third party OAuth provider.
256 |
257 | That's pretty nifty!
258 |
--------------------------------------------------------------------------------