├── .env.example ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package-lock.json └── package.json /.env.example: -------------------------------------------------------------------------------- 1 | # URLs of the API endpoint. You probably don't have to change this. 2 | authUrl = "https://login.sipgate.com/auth/realms/third-party/protocol/openid-connect/auth" 3 | tokenUrl = "https://login.sipgate.com/auth/realms/third-party/protocol/openid-connect/token" 4 | testApiEndpoint = "https://api.sipgate.com/v2/account" 5 | 6 | # The credentials of your Oauth Client. 7 | # Open https://console.sipgate.com/ and create a new client by clicking on "Neuer Client". 8 | # Enter the client ID and secret in the variables below. 9 | clientId = 'YOUR_CLIENT_ID' 10 | clientSecret = 'YOUT_CLIENT_SECRET' 11 | 12 | # Choose the scopes you want permission for. 13 | # You can find an overview of available scopes here: https://www.sipgate.io/en/rest-api/oauth2-scopes 14 | oauthScope = "account:read" 15 | 16 | # Choose a URL where a client will be redirected to after a successful login. 17 | # You also need to enter this URL as a redirect URI for your client on https://console.sipgate.com/clients 18 | redirectUri = "http://localhost:8080/oauth" 19 | 20 | # Choose the port where the built-in server should listen on. 21 | port = "8080" 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Run tests on pull request and auto merge dependabot pull requests 2 | 3 | on: pull_request 4 | 5 | permissions: write-all 6 | 7 | jobs: 8 | dependabot_merge: 9 | uses: sipgate-io/dependabot-automerge/.github/workflows/dependabot_automerge.yml@main 10 | secrets: inherit 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/vim,git,macos,linux,intellij+all,visualstudiocode,node 3 | # Edit at https://www.gitignore.io/?templates=vim,git,macos,linux,intellij+all,visualstudiocode,node 4 | 5 | ### Git ### 6 | # Created by git for backups. To disable backups in Git: 7 | # $ git config --global mergetool.keepBackup false 8 | *.orig 9 | 10 | # Created by git when using merge tools for conflicts 11 | *.BACKUP.* 12 | *.BASE.* 13 | *.LOCAL.* 14 | *.REMOTE.* 15 | *_BACKUP_*.txt 16 | *_BASE_*.txt 17 | *_LOCAL_*.txt 18 | *_REMOTE_*.txt 19 | 20 | ### Intellij+all ### 21 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 22 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 23 | 24 | # User-specific stuff 25 | .idea/**/workspace.xml 26 | .idea/**/tasks.xml 27 | .idea/**/usage.statistics.xml 28 | .idea/**/dictionaries 29 | .idea/**/shelf 30 | 31 | # Generated files 32 | .idea/**/contentModel.xml 33 | 34 | # Sensitive or high-churn files 35 | .idea/**/dataSources/ 36 | .idea/**/dataSources.ids 37 | .idea/**/dataSources.local.xml 38 | .idea/**/sqlDataSources.xml 39 | .idea/**/dynamic.xml 40 | .idea/**/uiDesigner.xml 41 | .idea/**/dbnavigator.xml 42 | 43 | # Gradle 44 | .idea/**/gradle.xml 45 | .idea/**/libraries 46 | 47 | # Gradle and Maven with auto-import 48 | # When using Gradle or Maven with auto-import, you should exclude module files, 49 | # since they will be recreated, and may cause churn. Uncomment if using 50 | # auto-import. 51 | # .idea/modules.xml 52 | # .idea/*.iml 53 | # .idea/modules 54 | 55 | # CMake 56 | cmake-build-*/ 57 | 58 | # Mongo Explorer plugin 59 | .idea/**/mongoSettings.xml 60 | 61 | # File-based project format 62 | *.iws 63 | 64 | # IntelliJ 65 | out/ 66 | 67 | # mpeltonen/sbt-idea plugin 68 | .idea_modules/ 69 | 70 | # JIRA plugin 71 | atlassian-ide-plugin.xml 72 | 73 | # Cursive Clojure plugin 74 | .idea/replstate.xml 75 | 76 | # Crashlytics plugin (for Android Studio and IntelliJ) 77 | com_crashlytics_export_strings.xml 78 | crashlytics.properties 79 | crashlytics-build.properties 80 | fabric.properties 81 | 82 | # Editor-based Rest Client 83 | .idea/httpRequests 84 | 85 | # Android studio 3.1+ serialized cache file 86 | .idea/caches/build_file_checksums.ser 87 | 88 | ### Intellij+all Patch ### 89 | # Ignores the whole .idea folder and all .iml files 90 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 91 | 92 | .idea/ 93 | 94 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 95 | 96 | *.iml 97 | modules.xml 98 | .idea/misc.xml 99 | *.ipr 100 | 101 | # Sonarlint plugin 102 | .idea/sonarlint 103 | 104 | ### Linux ### 105 | *~ 106 | 107 | # temporary files which can be created if a process still has a handle open of a deleted file 108 | .fuse_hidden* 109 | 110 | # KDE directory preferences 111 | .directory 112 | 113 | # Linux trash folder which might appear on any partition or disk 114 | .Trash-* 115 | 116 | # .nfs files are created when an open file is removed but is still being accessed 117 | .nfs* 118 | 119 | ### macOS ### 120 | # General 121 | .DS_Store 122 | .AppleDouble 123 | .LSOverride 124 | 125 | # Icon must end with two \r 126 | Icon 127 | 128 | # Thumbnails 129 | ._* 130 | 131 | # Files that might appear in the root of a volume 132 | .DocumentRevisions-V100 133 | .fseventsd 134 | .Spotlight-V100 135 | .TemporaryItems 136 | .Trashes 137 | .VolumeIcon.icns 138 | .com.apple.timemachine.donotpresent 139 | 140 | # Directories potentially created on remote AFP share 141 | .AppleDB 142 | .AppleDesktop 143 | Network Trash Folder 144 | Temporary Items 145 | .apdisk 146 | 147 | ### Node ### 148 | # Logs 149 | logs 150 | *.log 151 | npm-debug.log* 152 | yarn-debug.log* 153 | yarn-error.log* 154 | 155 | # Runtime data 156 | pids 157 | *.pid 158 | *.seed 159 | *.pid.lock 160 | 161 | # Directory for instrumented libs generated by jscoverage/JSCover 162 | lib-cov 163 | 164 | # Coverage directory used by tools like istanbul 165 | coverage 166 | 167 | # nyc test coverage 168 | .nyc_output 169 | 170 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 171 | .grunt 172 | 173 | # Bower dependency directory (https://bower.io/) 174 | bower_components 175 | 176 | # node-waf configuration 177 | .lock-wscript 178 | 179 | # Compiled binary addons (https://nodejs.org/api/addons.html) 180 | build/Release 181 | 182 | # Dependency directories 183 | node_modules/ 184 | jspm_packages/ 185 | 186 | # TypeScript v1 declaration files 187 | typings/ 188 | 189 | # Optional npm cache directory 190 | .npm 191 | 192 | # Optional eslint cache 193 | .eslintcache 194 | 195 | # Optional REPL history 196 | .node_repl_history 197 | 198 | # Output of 'npm pack' 199 | *.tgz 200 | 201 | # Yarn Integrity file 202 | .yarn-integrity 203 | 204 | # dotenv environment variables file 205 | .env 206 | .env.test 207 | 208 | # parcel-bundler cache (https://parceljs.org/) 209 | .cache 210 | 211 | # next.js build output 212 | .next 213 | 214 | # nuxt.js build output 215 | .nuxt 216 | 217 | # vuepress build output 218 | .vuepress/dist 219 | 220 | # Serverless directories 221 | .serverless/ 222 | 223 | # FuseBox cache 224 | .fusebox/ 225 | 226 | # DynamoDB Local files 227 | .dynamodb/ 228 | 229 | ### Vim ### 230 | # Swap 231 | [._]*.s[a-v][a-z] 232 | [._]*.sw[a-p] 233 | [._]s[a-rt-v][a-z] 234 | [._]ss[a-gi-z] 235 | [._]sw[a-p] 236 | 237 | # Session 238 | Session.vim 239 | 240 | # Temporary 241 | .netrwhist 242 | # Auto-generated tag files 243 | tags 244 | # Persistent undo 245 | [._]*.un~ 246 | 247 | ### VisualStudioCode ### 248 | .vscode/* 249 | !.vscode/settings.json 250 | !.vscode/tasks.json 251 | !.vscode/launch.json 252 | !.vscode/extensions.json 253 | 254 | ### VisualStudioCode Patch ### 255 | # Ignore all local history of files 256 | .history 257 | 258 | # End of https://www.gitignore.io/api/vim,git,macos,linux,intellij+all,visualstudiocode,node -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sipgate logo 2 | 3 | # sipgate.io Node.js OAuth example 4 | To demonstrate how to authenticate against the sipgate REST API using the OAuth mechanism, 5 | we make use of the `/authorization/userinfo` endpoint which provides information about the user. 6 | 7 | > For further information regarding the sipgate REST API please visit https://api.sipgate.com/v2/doc 8 | 9 | For educational purposes we do not use an OAuth client library in this example, but if you plan to implement authentication using OAuth in you application we recommend using one. You can find various client libraries here: [https://oauth.net/code/](https://oauth.net/code/). 10 | 11 | 12 | ## What is OAuth and when to use it 13 | OAuth is a standard protocol for authorization. You can find more information on the OAuth website [https://oauth.net/](https://oauth.net/) or on wikipedia [https://en.wikipedia.org/wiki/OAuth](https://en.wikipedia.org/wiki/OAuth). 14 | 15 | Applications that use the sipgate REST API on behalf of another user should use the OAuth authentication method instead of Basic Auth. 16 | 17 | 18 | ## Prerequisites 19 | - Node.js >= 10.15.3 20 | 21 | 22 | ## Setup OAuth with sipgate 23 | In order to authenticate against the sipgate REST API via OAuth you first need to create a Client in the sipgate Web App. 24 | 25 | You can create a client as follows: 26 | 27 | 1. Navigate to [console.sipgate.com](https://console.sipgate.com/) and login with your sipgate account credentials. 28 | 2. Make sure you are in the **Clients** tab in the left side menu 29 | 3. Click the **New client** button 30 | 4. Fill out the **New client** dialog (Find information about the Privacy Policy URL and Terms of use URL [here](#privacy-policy-url-and-terms-of-use-url)) 31 | 5. The **Clients** list should contain your new client 32 | 6. Select your client 33 | 7. The entries **Id** and **Secret** are `YOUR_CLIENT_ID` and `YOUR_CLIENT_SECRET` required for the configuration of your application (see [Configuration](#configuration)) 34 | 8. Now you just have to add your `REDIRECT_URI` to your Client by clicking the **Add redirect uri** button and fill in the dialog. In our example we provide a server within the application itself so we use `http://localhost:{port}/oauth` (the default port is `8080`). 35 | 36 | Now your Client is ready to use. 37 | 38 | 39 | ### Privacy Policy URL and Terms of use URL 40 | In the Privacy Policy URL and Terms of use URL you must supply in the **New Client** dialog when creating a new Client to use with OAuth you must supply the Privacy Policy URL and Terms of use URL of the Service you want to use OAuth authorization for. During development and testing you can provide any valid URL but later you must change them. 41 | 42 | 43 | ## Configuration 44 | Create the .env file by copying the .env.example. Set the values according to the comment above each variable. 45 | 46 | The `oauth_scope` defines what kind of access your Client should have to your account and is specific to your respective application. In this case, since we only want to get your basic account information as an example, the scope `account:read` is sufficient. 47 | 48 | ``` 49 | oauth_scope=account:read 50 | ``` 51 | > Visit https://developer.sipgate.io/rest-api/oauth2-scopes/ to see all available scopes 52 | 53 | The `redirect_uri` which we have previously used in the creation of our Client is supplied to the sipgate login page to specify where you want to be redirected after successful login. As explained above, our application provides a small web server itself that handles HTTP requests directed at `http://localhost:8080/oauth`. In case there is already a service listening on port `8080` of your machine you can choose a different port number, but be sure to adjust both the `redirect_uri` and the `port` property accordingly. 54 | 55 | 56 | ## Install dependencies 57 | Navigate to the project's root directory and run: 58 | ```bash 59 | $ npm install 60 | ``` 61 | 62 | 63 | ## Execution 64 | Run the application: 65 | ```bash 66 | $ npm start 67 | ``` 68 | 69 | 70 | ## How It Works 71 | The main function of our application looks like this: 72 | 73 | In the [index.js](./index.js) we first load the environment variables from [.env](./.env). 74 | ```javascript 75 | require('dotenv').config() 76 | const config = process.env 77 | ``` 78 | 79 | We then generate a unique identifier `sessionState` for our authorization process so that we can match a server response to our request later. The authorization URI is composed from the properties previously loaded from the configuration file and printed to the console. 80 | ```javascript 81 | const sessionState = uuidv4(); 82 | 83 | const params = { 84 | client_id: config.clientId, 85 | redirect_uri: config.redirectUri, 86 | scope: config.oauthScope, 87 | response_type: 'code', 88 | state: sessionState, 89 | }; 90 | 91 | const queryString = querystring.stringify(params); 92 | const apiAuthUrl = `${config.authUrl}?${queryString}`; 93 | 94 | console.log(`Please open the following URL in your browser: \n${apiAuthUrl}`); 95 | ``` 96 | 97 | Opening the link in your browser takes you to the sipgate login page where you need to confirm the scope that your Client is requesting access to before logging in with your sipgate credentials. You are then redirected to `http://localhost:8080/oauth` and our application's web server receives your request. 98 | 99 | We create a webserver and pass the `handleRequest` function which should be used for processing the incoming requests. 100 | ```javascript 101 | const server = http.createServer(handleRequest); 102 | 103 | server.listen(config.port, () => { 104 | console.log('Server listening on: http://localhost:%s', config.port); 105 | }); 106 | ``` 107 | 108 | The function `handleRequest` handles all incoming HTTP requests. 109 | ```javascript 110 | const handleRequest = async (request, response) => { 111 | const requestUrl = url.parse(request.url); 112 | 113 | if (requestUrl.pathname !== '/oauth') { 114 | response.end(); 115 | return; 116 | } 117 | 118 | const queryParameter = querystring.parse(requestUrl.query); 119 | const authorizationCode = queryParameter.code; 120 | const receivedState = queryParameter.state; 121 | 122 | if (receivedState !== sessionState) { 123 | console.log('State in the callback does not match the state in the original request.'); 124 | 125 | response.end(); 126 | return; 127 | } 128 | 129 | // Get access token 130 | const tokens = await retrieveTokens(authorizationCode); 131 | 132 | // Get user information 133 | const userInformation = await userInfo(tokens.accessToken); 134 | 135 | // Refresh tokens 136 | const refreshedTokens = await refreshTokens(tokens.refreshToken); 137 | 138 | // Get user information using the refreshed accessToken 139 | const userInformationWithRefreshedToken = await userInfo(refreshedTokens.accessToken); 140 | 141 | response.end(); 142 | }; 143 | ``` 144 | After checking if the pathname of the request url matches `/oauth` we extract the query parameters from the request received from the browser and verify that the state transmitted by the authorization server matches the one initially supplied. In the case of multiple concurrent authorization processes this state also serves to match pairs of request and response. We use the code obtained from the request to fetch a set of tokens from the authorization server and try them out by making an request to the `/authorization/userinfo` endpoint of the REST API. Lastly, we use the refresh token to obtain another set of tokens. Note that this invalidates the previous set. 145 | 146 | The `retrieveTokens` function fetches the tokens from the authorization server. 147 | ```javascript 148 | const retrieveTokens = async authorizationCode => { 149 | const requestBody = { 150 | client_id: config.clientId, 151 | client_secret: config.clientSecret, 152 | redirect_uri: config.redirectUri, 153 | code: authorizationCode, 154 | grant_type: 'authorization_code', 155 | }; 156 | 157 | const response = await axios.post(config.tokenUrl, querystring.stringify(requestBody), { 158 | headers: { 159 | 'Content-Type': 'application/x-www-form-urlencoded', 160 | }, 161 | }); 162 | 163 | return { 164 | accessToken: response.data.access_token, 165 | refreshToken: response.data.refresh_token, 166 | }; 167 | }; 168 | ``` 169 | We use Axios to send a POST-Request to the authorization server to obtain a set of tokens (Access-Token and Refresh-Token). The POST-Request must contain the `client_id`, `client_secret`, `redirect_uri`, `code` and `grant_type` as form data. 170 | 171 | The `refreshTokens` function is very similar to the `retrieveTokens` function. It differs in that we set the `grant_type` to `refresh_token` to indicate that we want to refresh our token, and provide the `refresh_token` we got from the `retrieveTokens` function instead of the `code`. 172 | > ```javascript 173 | > ... 174 | > refresh_token: refreshToken, 175 | > grant_type: 'refresh_token', 176 | > ... 177 | > ``` 178 | 179 | To see if authorization with the token works, we query the `/authorization/userinfo` endpoint of the REST API. 180 | ```javascript 181 | const userInfo = async accessToken => { 182 | const options = { 183 | headers: { 184 | Authorization: `Bearer ${accessToken}`, 185 | }, 186 | }; 187 | 188 | const response = await axios.get(config.testApiEndpoint, options); 189 | 190 | return response.data; 191 | }; 192 | ``` 193 | To use the token for authorization we set the `Authorization` header to `Bearer` followed by a space and the `accessToken` we obtained with the `retrieveTokens` or `refreshTokens` function. 194 | 195 | 196 | ## Common Issues 197 | 198 | ### "State in the callback does not match the state in the original request" 199 | Possible reasons are: 200 | - the application was restarted and you used old url again or refreshed the browser tab 201 | 202 | 203 | ### "Error: listen EADDRINUSE: address already in use :::{port}" 204 | Possible reasons are: 205 | - another instance of the application is running 206 | - the port configured in the [config.json](./config.json) file is used by another application 207 | 208 | 209 | ### "Error: listen EACCES: permission denied 0.0.0.0:{port}" 210 | Possible reasons are: 211 | - you do not have the permission to bind to the specified port. This can happen if you use port 80, 443 or another well-known port which you can only bind to if you run the application with superuser privileges 212 | 213 | 214 | ### "invalid parameter: redirect_uri" 215 | Possible reasons are: 216 | - the redirect_uri in the [config.json](./config.json) is invalid or not set 217 | - the redirect_uri is not correctly configured the sipgate Web App (You can find more information about the configuration process in the [Setup OAuth with sipgate](#setup-oauth-with-sipgate) section) 218 | 219 | 220 | ### "client not found" or "invalid client_secret" 221 | Possible reasons are: 222 | - the client_id or client_secret configured in the [config.json](./config.json) is invalid. You can check them in the sipgate Web App. See [Setup OAuth with sipgate](#setup-oauth-with-sipgate) 223 | 224 | 225 | ## Related 226 | + [OAuth RFC6749](https://tools.ietf.org/html/rfc6749) 227 | + [oauth.net](https://oauth.net/) 228 | + [auth0.com/docs/](https://auth0.com/docs/) 229 | + [github.com/axios/axios](https://github.com/axios/axios) 230 | 231 | 232 | ## Contact Us 233 | Please let us know how we can improve this example. 234 | If you have a specific feature request or found a bug, please use **Issues** or fork this repository and send a **pull request** with your improvements. 235 | 236 | 237 | ## License 238 | This project is licensed under **The Unlicense** (see [LICENSE file](./LICENSE)). 239 | 240 | 241 | ## External Libraries 242 | This code uses the following external libraries 243 | 244 | + axios: 245 | + Licensed under the [MIT License](https://opensource.org/licenses/MIT) 246 | + Website: https://github.com/axios/axios 247 | 248 | + uuid: 249 | + Licensed under the [MIT License](https://opensource.org/licenses/MIT) 250 | + Website: https://github.com/kelektiv/node-uuid 251 | 252 | 253 | ---- 254 | [sipgate.io](https://www.sipgate.io) | [@sipgateio](https://twitter.com/sipgateio) | [API-doc](https://api.sipgate.com/v2/doc) 255 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const querystring = require('querystring'); 2 | const http = require('http'); 3 | const uuidv4 = require('uuid/v4'); 4 | const url = require('url'); 5 | const axios = require('axios'); 6 | 7 | require('dotenv').config() 8 | 9 | const sessionState = uuidv4(); 10 | 11 | const config = process.env 12 | 13 | const params = { 14 | client_id: config.clientId, 15 | redirect_uri: config.redirectUri, 16 | scope: config.oauthScope, 17 | response_type: 'code', 18 | state: sessionState, 19 | }; 20 | 21 | const queryString = querystring.stringify(params); 22 | const apiAuthUrl = `${config.authUrl}?${queryString}`; 23 | 24 | console.log(`Please open the following URL in your browser: \n${apiAuthUrl}`); 25 | 26 | const retrieveTokens = async authorizationCode => { 27 | const requestBody = { 28 | client_id: config.clientId, 29 | client_secret: config.clientSecret, 30 | redirect_uri: config.redirectUri, 31 | code: authorizationCode, 32 | grant_type: 'authorization_code', 33 | }; 34 | 35 | const response = await axios.post(config.tokenUrl, querystring.stringify(requestBody), { 36 | headers: { 37 | 'Content-Type': 'application/x-www-form-urlencoded', 38 | }, 39 | }); 40 | 41 | return { 42 | accessToken: response.data.access_token, 43 | refreshToken: response.data.refresh_token, 44 | }; 45 | }; 46 | 47 | const refreshTokens = async refreshToken => { 48 | const requestBody = { 49 | client_id: config.clientId, 50 | client_secret: config.clientSecret, 51 | refresh_token: refreshToken, 52 | grant_type: 'refresh_token', 53 | }; 54 | 55 | const response = await axios.post(config.tokenUrl, querystring.stringify(requestBody), { 56 | headers: { 57 | 'Content-Type': 'application/x-www-form-urlencoded', 58 | }, 59 | }); 60 | 61 | return { 62 | accessToken: response.data.access_token, 63 | refreshToken: response.data.refresh_token, 64 | }; 65 | }; 66 | 67 | const userInfo = async accessToken => { 68 | const options = { 69 | headers: { 70 | Authorization: `Bearer ${accessToken}`, 71 | }, 72 | }; 73 | 74 | const response = await axios.get(config.testApiEndpoint, options); 75 | 76 | return response.data; 77 | }; 78 | 79 | const handleRequest = async (request, response) => { 80 | const requestUrl = url.parse(request.url); 81 | 82 | if (requestUrl.pathname !== '/oauth') { 83 | response.end(); 84 | return; 85 | } 86 | 87 | const queryParameter = querystring.parse(requestUrl.query); 88 | const authorizationCode = queryParameter.code; 89 | const receivedState = queryParameter.state; 90 | 91 | if (receivedState !== sessionState) { 92 | console.log('State in the callback does not match the state in the original request.'); 93 | 94 | response.end(); 95 | return; 96 | } 97 | 98 | // Get access token 99 | console.log('Getting tokens...'); 100 | const tokens = await retrieveTokens(authorizationCode); 101 | console.log('Received new tokens: \n', tokens); 102 | 103 | // Get user information 104 | console.log('Getting user information...'); 105 | const userInformation = await userInfo(tokens.accessToken); 106 | console.log(userInformation); 107 | 108 | // Refresh tokens 109 | console.log('Refreshing tokens...'); 110 | const refreshedTokens = await refreshTokens(tokens.refreshToken); 111 | console.log('Received new tokens: \n', tokens); 112 | 113 | // Get user information using the refreshed accessToken 114 | console.log('Getting user information...'); 115 | const userInformationWithRefreshedToken = await userInfo(refreshedTokens.accessToken); 116 | console.log(userInformationWithRefreshedToken); 117 | 118 | response.end(); 119 | }; 120 | 121 | const server = http.createServer(handleRequest); 122 | 123 | server.listen(config.port, () => { 124 | console.log('Server listening on: http://localhost:%s', config.port); 125 | }); 126 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sipgateio-oauth-node", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "sipgateio-oauth-node", 9 | "version": "1.0.0", 10 | "license": "Unlicense", 11 | "dependencies": { 12 | "axios": "^0.21.2", 13 | "dotenv": "^16.0.2", 14 | "uuid": "^3.3.2" 15 | }, 16 | "engines": { 17 | "node": "10.15.3" 18 | } 19 | }, 20 | "node_modules/axios": { 21 | "version": "0.21.2", 22 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.2.tgz", 23 | "integrity": "sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==", 24 | "dependencies": { 25 | "follow-redirects": "^1.14.0" 26 | } 27 | }, 28 | "node_modules/dotenv": { 29 | "version": "16.0.2", 30 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.2.tgz", 31 | "integrity": "sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==", 32 | "engines": { 33 | "node": ">=12" 34 | } 35 | }, 36 | "node_modules/follow-redirects": { 37 | "version": "1.15.1", 38 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", 39 | "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", 40 | "funding": [ 41 | { 42 | "type": "individual", 43 | "url": "https://github.com/sponsors/RubenVerborgh" 44 | } 45 | ], 46 | "engines": { 47 | "node": ">=4.0" 48 | }, 49 | "peerDependenciesMeta": { 50 | "debug": { 51 | "optional": true 52 | } 53 | } 54 | }, 55 | "node_modules/uuid": { 56 | "version": "3.3.3", 57 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", 58 | "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", 59 | "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", 60 | "bin": { 61 | "uuid": "bin/uuid" 62 | } 63 | } 64 | }, 65 | "dependencies": { 66 | "axios": { 67 | "version": "0.21.2", 68 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.2.tgz", 69 | "integrity": "sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==", 70 | "requires": { 71 | "follow-redirects": "^1.14.0" 72 | } 73 | }, 74 | "dotenv": { 75 | "version": "16.0.2", 76 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.2.tgz", 77 | "integrity": "sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==" 78 | }, 79 | "follow-redirects": { 80 | "version": "1.15.1", 81 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", 82 | "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" 83 | }, 84 | "uuid": { 85 | "version": "3.3.3", 86 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", 87 | "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sipgateio-oauth-node", 3 | "version": "1.0.0", 4 | "description": "A demonstration on how to authenticate against the sipgate REST API using the OAuth mechanism.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "keywords": [], 10 | "author": "sipgate.io Team ", 11 | "repository": "https://github.com/sipgate-io/sipgateio-oauth-node", 12 | "engines": { 13 | "node": "10.15.3" 14 | }, 15 | "license": "Unlicense", 16 | "dependencies": { 17 | "axios": "^0.21.2", 18 | "dotenv": "^16.0.2", 19 | "uuid": "^3.3.2" 20 | } 21 | } 22 | --------------------------------------------------------------------------------