-
Notifications
You must be signed in to change notification settings - Fork 10
Add authentication sample using web application flow #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
6 changes: 6 additions & 0 deletions
6
rest-api/javascript/authentication/web-application-flow/.env.example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# Base Url endpoint | ||
BASE_URL = 'https://api-sandbox.uphold.com' | ||
|
||
CLIENT_ID = '' | ||
CLIENT_SECRET = '' | ||
SERVER_PORT = 3000 |
2 changes: 2 additions & 0 deletions
2
rest-api/javascript/authentication/web-application-flow/.gitignore
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.env | ||
node_modules/ |
52 changes: 52 additions & 0 deletions
52
rest-api/javascript/authentication/web-application-flow/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# Authorization code flow | ||
|
||
This sample project demonstrates how a registered app can request authorization from Uphold users to perform actions on their behalf, | ||
by using the [authorization code OAuth flow](https://oauth.net/2/grant-types/authorization-code/). | ||
For further background, please refer to the [API documentation](https://uphold.com/en/developer/api/documentation). | ||
|
||
## Summary | ||
|
||
This flow is **recommended for web applications** that wish to retrieve information about a user's Uphold account, | ||
or take actions on their behalf. | ||
|
||
This process, sometimes called "3-legged OAuth", requires three steps, each initiated by one of the three actors: | ||
|
||
1. The **user** navigates to Uphold's website, following an authorization URL generated by the app, | ||
where they log in and authorize the app to access their Uphold account; | ||
2. **Uphold** redirects the user back to the app's website, including a short-lived authorization code in the URL; | ||
3. The **application**'s server submits this code to Uphold's API, obtaining a long-lived access token in response. | ||
(Since this final step occurs in server-to-server communication, the actual code is never exposed to the browser.) | ||
|
||
This example sets up a local server that can be used to perform the OAuth web application flow cycle as described above. | ||
|
||
## Requirements | ||
|
||
To run this example, you must have: | ||
|
||
- Node.js v13.14.0 or later | ||
- An account at <https://sandbox.uphold.com> | ||
|
||
## Setup | ||
|
||
- Run `npm install` (or `yarn install`) | ||
- [Create an app on Uphold Sandbox](https://sandbox.uphold.com/dashboard/profile/applications/developer/new) | ||
with the redirect URI field set to `https://localhost:3000/callback` | ||
(you may use a different port number, if you prefer). | ||
Note that this demo expects at least the `user:read` scope to be activated. | ||
- Create a `.env` file based on the `.env.example` file, and populate it with the required data. | ||
Make sure to also update the `SERVER_PORT` if you changed it in the previous step. | ||
|
||
## Run | ||
|
||
- Run `node index.js` | ||
- Open the URL printed in the command line. | ||
- **Attention:** Since the certificate used in this demo is self-signed, not all browsers will allow navigating to the page. | ||
You can use Firefox or Safari, which will display a warning but allow you to proceed regardless. | ||
Alternatively, you can navigate to `chrome://flags/#allow-insecure-localhost` in Chromium-based browsers, | ||
to toggle support for self-signed localhost certificates. | ||
- Click the link in the page to navigate to Uphold's servers. | ||
- Accept the application's permission request. | ||
|
||
Once the authorization is complete and an access token is obtained, | ||
the local server will use it to make a test request to the Uphold API. | ||
The output will be printed in the command line. |
90 changes: 90 additions & 0 deletions
90
rest-api/javascript/authentication/web-application-flow/authorization-code-flow.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/** | ||
* Dependencies. | ||
*/ | ||
|
||
import axios from "axios"; | ||
import b64Pkg from "js-base64"; | ||
import dotenv from "dotenv"; | ||
import qs from "qs"; | ||
import path from "path"; | ||
|
||
const { encode } = b64Pkg; | ||
|
||
/** | ||
* Dotenv configuration. | ||
*/ | ||
|
||
dotenv.config({ path: path.resolve() + "/.env" }); | ||
|
||
/** | ||
* Compose error page. | ||
*/ | ||
|
||
export function composeErrorPage(data, state) { | ||
let content = "<h1>Something went wrong.</h1>"; | ||
|
||
if (data.state && data.state !== state) { | ||
content += | ||
`<p>The received state (<code>${data.state}</code>) | ||
does not match the expected value: <code>${state}</code>.</p>`; | ||
} else if (Object.values(data).length) { | ||
content += "<p>Here's what Uphold's servers returned:</p>"; | ||
content += `<pre>${JSON.stringify(data, null, 4)}</pre>`; | ||
} else { | ||
content += "<p>This page should be reached at the end of an OAuth authorization process.</p>"; | ||
content += "<p>Please confirm that you followed the steps in the README.</p>"; | ||
} | ||
|
||
return content; | ||
} | ||
|
||
/** | ||
* Get assets. | ||
*/ | ||
|
||
export async function getAssets(token) { | ||
try { | ||
const response = await axios.get(`${process.env.BASE_URL}/v0/assets`, { | ||
headers: { | ||
Authorization: `${token.token_type} ${token.access_token}`, | ||
}, | ||
}); | ||
return response.data; | ||
} catch (error) { | ||
console.log(JSON.stringify(error, null, 2)); | ||
throw error; | ||
} | ||
} | ||
|
||
/** | ||
* Get Token. | ||
*/ | ||
|
||
export async function getToken(code) { | ||
// Base64-encoded authentication credentials | ||
const auth = encode(process.env.CLIENT_ID + ":" + process.env.CLIENT_SECRET); | ||
|
||
// set POST options for Axios | ||
const options = { | ||
method: "POST", | ||
headers: { | ||
Authorization: "Basic " + auth, | ||
"content-type": "application/x-www-form-urlencoded", | ||
}, | ||
data: qs.stringify({ code, grant_type: "client_credentials" }), | ||
url: `${process.env.BASE_URL}/oauth2/token`, | ||
}; | ||
|
||
const data = axios(options) | ||
.then((response) => { | ||
return response.data; | ||
}) | ||
.catch((error) => { | ||
error.response.data.errors | ||
? console.log(JSON.stringify(error.response.data.errors, null, 2)) | ||
: console.log(JSON.stringify(error, null, 2)); | ||
throw error; | ||
}); | ||
|
||
return data; | ||
} |
27 changes: 27 additions & 0 deletions
27
rest-api/javascript/authentication/web-application-flow/cert.pem
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
-----BEGIN CERTIFICATE----- | ||
MIIEljCCAn4CCQCHHmqArzJctTANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJw | ||
dDAeFw0yMDEwMjIxMzE1MTlaFw0yMTEwMjIxMzE1MTlaMA0xCzAJBgNVBAYTAnB0 | ||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArsxVWb12Rmn7oocPIXe5 | ||
SgiiRAqS4pZ2jnpxkN2rYp8sBBZCYAkRFNfIh+SUoOVPUhrM5JDZ9stmPHAba/XO | ||
oOhhq4paI082TqBiBqqjYtofqdKp3FjMLXcV+Y1jYnphZ6U+eZKsLUc0hWCK/TFa | ||
6H5Y3EUip9t8xmNRkzo2xhd5gNTEsvFMx/1XzJVJ7T5kgjKF+SECEw3leQva1RPK | ||
g7+REF1McZlWqav8Y/qEQ3ZS1AIJLEHG0x1rSE9zf0PHiTYecPbYYLwMccHorKua | ||
owjQKt9MboFN5u+Ne99sTyLuemDBmBFEI87ecBBCDzQb/bOQoqKJDY4u1xjO5Crf | ||
tvVgZoGkwlXobJZ4r02Zfh338dXCIbTw9tXNV0sGtdiDMD3uPDq7+pU7mjWIcWjb | ||
1hNVFxOO5Bna42r8q53QkfibKTVEZZaZmOu9vosOBGa4YsYUXvh0N1TqS7jNYv2H | ||
vTdKYZnJvEoFj4bXEpyA8Dk/roybij19l0d5w6SR34Aq1M63NxGwph4CiCJ2SMGU | ||
u+y048/XH64Bn1GjE19yyZ8JKi6tiENY6m9WS4BDGFiAOL5XkthcUmI5j8yaHncr | ||
iSIJmfEiwL3ZCiUzD3Ua3l5oSK+aG5hf7FU9rXNYnt6byerrZasZnGi8U4y4CzLA | ||
VEdfuklz3fBUVqz2fU6cFEkCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAW9j6giBn | ||
iiphY4GMuQlQr3mf/rrtqCDIV+SAkhi/IzKbk4x/5yXoLZ2r9FbcZmNigBjQqB16 | ||
V0YN2lNYFiSF9Sx9Qv8XIFXYyPIKucHKekGASDk8oqmPtHQYBH9hOTRN8SRaT7aQ | ||
rV1pYqdkjjG4gtjauYTAXucgQjP7d4kj8jOadZCffN53/6ASPRkj/Q+vpUlj0dxd | ||
tjrEi1NxwbHahi59UggTg6ftLTgIMOHJYWMyTuR++B8m+UT6bFpPxB5enfcL+Qg9 | ||
4cTK0MtebyIIXmXv2L5S56/En+Kvlq3ynRFlqq9kdHK80kqjmPw6D2A+RHka1nDb | ||
uo61ZPxBznMk9s8SJix+lv3MvinOJCiJDjYhef0rZXSSUEmXa58IF7iZdV+SIlUp | ||
bEbEpCvVqBgc8XDoVcSp96rpZDSuSYfU7Xz9McyFbOtq+NkEtDevxE8r3WqIBh9x | ||
efss+CBkrdGyj5qyBTd8YyLKvY3fsPfS08BMN7cMZVw8wsICymAGUFHk5Do3RFxM | ||
tgD1VE26v0cluQwguYWZgRLR9lK1vREs7OfRb4RaXLczArOza5o4JTwmPByZ7owT | ||
PzK3H9ydn9oq8LoRoY+9s3IRgdRQSD/idf/QylsZ9Es4av9LO+6pvmO+Sr/rnIQb | ||
Gg6t7OPvwYNi62kS7eywQNbbfIB6wX0h/iw= | ||
-----END CERTIFICATE----- |
81 changes: 81 additions & 0 deletions
81
rest-api/javascript/authentication/web-application-flow/index.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/** | ||
* Dependencies. | ||
*/ | ||
|
||
import dotenv from "dotenv"; | ||
import express from "express"; | ||
import fs from "fs"; | ||
import https from "https"; | ||
import path from "path"; | ||
import { randomBytes } from "crypto"; | ||
import { composeErrorPage, getAssets, getToken } from "./authorization-code-flow.js"; | ||
|
||
/** | ||
* Dotenv configuration. | ||
*/ | ||
|
||
dotenv.config({ path: path.resolve() + "/.env" }); | ||
|
||
/** | ||
* Server configuration. | ||
*/ | ||
|
||
const app = express(); | ||
const port = process.env.SERVER_PORT || 3000; | ||
const state = randomBytes(8).toString('hex'); | ||
|
||
/** | ||
* Main page. | ||
*/ | ||
|
||
app.get("/", async (req, res) => { | ||
// Compose the authorization URL. This assumes the `user:read` scope has been activated for this application. | ||
const authorizationUrl = 'https://sandbox.uphold.com/authorize/' | ||
+ process.env.CLIENT_ID | ||
+ '?scope=user:read' | ||
+ '&state=' + state; | ||
|
||
res.send( | ||
`<h1>Demo app server</h1> | ||
<p>Please <a href="${authorizationUrl}">authorize this app</a> on Uphold's Sandbox.</p>` | ||
); | ||
}); | ||
|
||
|
||
/** | ||
* Callback URL endpoint. | ||
*/ | ||
|
||
app.get("/callback", async (req, res) => { | ||
// Show an error page if the code wasn't returned or the state doesn't match what we sent. | ||
if (!req.query.code || req.query.state !== state) { | ||
res.send(composeErrorPage(req.query, state)); | ||
} | ||
|
||
// Exchange the short-lived authorization code for a long-lived access token. | ||
const token = await getToken(req.query.code); | ||
console.log(`Authorization code ${req.query.code} successfully exchanged for access token:`, token); | ||
|
||
// Test the new token by making a call to the API. | ||
const assets = await getAssets(token); | ||
console.log("Output from test API call:", assets[0]); | ||
|
||
res.send( | ||
`<h1>Success!</h1> | ||
<p>The OAuth authorization code has been successfully exchanged for an access token.</p>` | ||
); | ||
}); | ||
|
||
/** | ||
* Run server. | ||
*/ | ||
|
||
https | ||
.createServer({ | ||
key: fs.readFileSync("./key.pem"), | ||
cert: fs.readFileSync("./cert.pem"), | ||
passphrase: "test", | ||
}, app) | ||
.listen(port, () => { | ||
console.log(`Server running at https://localhost:${port}`); | ||
}); |
54 changes: 54 additions & 0 deletions
54
rest-api/javascript/authentication/web-application-flow/key.pem
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
-----BEGIN ENCRYPTED PRIVATE KEY----- | ||
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 | ||
idnWYlp+GmR0sckgYM6+f3R+PQ== | ||
-----END ENCRYPTED PRIVATE KEY----- |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.