Integrating ChainLetter Login Using Node.js: Step-by-Step Guide
Incorporating a login system into your website using ChainLetter is straightforward. This guide will walk you through creating a ChainLetter login button on your site using a Node.js server.
Remember, while this Node.js example can help you understand the integration process, always implement enhanced security measures in your production environment. Let’s dive in!
Step 1: Project Setup and Dependencies
Begin by setting up your Node.js environment and installing necessary dependencies:
npm init -y npm install express body-parser dotenv
npm init -y npm install express body-parser dotenv
Security Tip: Keep sensitive data out of your codebase. Use environment variables and the dotenv package to load them. This minimizes the risk of compromising your API keys and secrets.
Step 2: Server Setup
In index.js, create your server using Express:
const express = require("express");
require("dotenv").config();
const app = express();
const bodyParser = require("body-parser");
app.use(bodyParser.json({ limit: "2mb" }));
// ... Your Methods and Routes go here
// Serve static files
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "/index.html"));
});
app.get("/style.css", (req, res) => {
res.sendFile(path.join(__dirname, "/style.css"));
});
// Start the server
app.listen(3111, "0.0.0.0", () => {
console.log(`Listening to requests on http://localhost:3111`);
});
Step 3: Registration and Token Retrieval Functions
Implement functions to interact with the ChainLetter API:
// Register user with ChainLetter Server API
async function RegisterUser(user) {
if (!user.userRegistered) {
const regUser = await fetch(
`${process.env.CL_APIURL}/api/v1/server/register_user`,
{
headers: {
apikey: process.env.CL_APIKEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
remote_uuid: user.username, // This must be unique on your server
remote_email: user.email,
}),
method: "POST",
}
);
// Store this in your DB so you don't need to keep incurring round trip latency to ChainLetter
return regUser.ok;
}
return true;
}
// Get user token from ChainLetter Server API
async function GetUserToken(user) {
const loginUser = await fetch(
`${process.env.CL_APIURL}/api/v1/server/get_user_token`,
{
headers: {
apikey: process.env.CL_APIKEY,
remote_uuid: user.username, // This must be unique in your application
"Content-Type": "application/json",
},
method: "GET",
}
);
if (!loginUser.ok) {
return null;
}
const loginUserResponse = await loginUser.json();
if (!loginUserResponse.success) {
return null;
}
return loginUserResponse.token;
}
Security Tip: Never expose API secrets to the client-side. Always make API requests server-side and store any persistent state in your secure database.
Step 4: User Login Handling
Construct a route to manage user login requests:
// Login route
app.post("/login", async (req, res) => {
if (!req.body.username) {
res.status(400).send({ message: "Username required" });
return;
}
// Mock user login
if (req.body.username == "johndoe123" && req.body.password == "password") {
let currentUser = {
name: "John Doe",
email: "[email protected]",
username: "johndoe123",
userRegistered: false,
};
// !!! This is the important part !!!
// Register user with ChainLetter, get a token, then send the token to the client
// This doesn't need to be in the login route, it can be in any other server route
if ((await RegisterUser(currentUser)) === false) {
return res.send({ message: "User Registration Failed." });
}
const token = await GetUserToken(currentUser);
if (!token) {
return res.send({ message: "Token Request Failed." });
}
// By embedding the token in the form, the client can submit the form to ChainLetter via POST, allowing the cookie to be set
return res.send({
message: "Logged in",
form: `
<form action="${
process.env.CL_APIURL
}/api/v1/client/post_user_token" method="POST">
<div class="form-group">
<input name="authorization" type="hidden" value="AUTH ${token}" />
<input name="return_url" type="hidden" value="${
req.protocol
}://${req.get("host")}" />
<input type="submit" value="Access ChainLetter" />
</div>
</form>
`,
});
}
res.send({ message: "Incorrect Login or Password" });
});
Security Tip: For a real-world application, use password hashing and validation against a user database. Avoid hardcoding credentials.
Step 5: Returning the Login Form
On successful authentication, note the return is a form that allows the client to post to ChainLetter:
...
// After successful authentication and token retrieval
return res.send({
message: "Logged in",
form: `
<form action="${
process.env.CL_APIURL
}/api/v1/client/post_user_token" method="POST">
<div class="form-group">
<input name="authorization" type="hidden" value="AUTH ${token}" />
<input name="return_url" type="hidden" value="${
req.protocol
}://${req.get("host")}" />
<input type="submit" value="Access ChainLetter" />
</div>
</form>
`,
});
Step 6: Running the Application in Production
Here is the complete index.js file:
const express = require("express");
require("dotenv").config({ path: ".env" });
const app = express();
const path = require("path");
const bodyParser = require("body-parser");
app.use(bodyParser.json({ limit: "2mb" }));
// Register user with ChainLetter Server API
async function RegisterUser(user) {
if (!user.userRegistered) {
const regUser = await fetch(
`${process.env.CL_APIURL}/api/v1/server/register_user`,
{
headers: {
apikey: process.env.CL_APIKEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
remote_uuid: user.username, // This must be unique on your server
remote_email: user.email,
}),
method: "POST",
}
);
// Store this in your DB so you don't need to keep incurring round trip latency to ChainLetter
return regUser.ok;
}
return true;
}
// Get user token from ChainLetter Server API
async function GetUserToken(user) {
const loginUser = await fetch(
`${process.env.CL_APIURL}/api/v1/server/get_user_token`,
{
headers: {
apikey: process.env.CL_APIKEY,
remote_uuid: user.username, // This must be unique in your application
"Content-Type": "application/json",
},
method: "GET",
}
);
if (!loginUser.ok) {
return null;
}
const loginUserResponse = await loginUser.json();
if (!loginUserResponse.success) {
return null;
}
return loginUserResponse.token;
}
// Login route
app.post("/login", async (req, res) => {
if (!req.body.username) {
res.status(400).send({ message: "Username required" });
return;
}
// Mock user login
if (req.body.username == "johndoe123" && req.body.password == "password") {
let currentUser = {
name: "John Doe",
email: "[email protected]",
username: "johndoe123",
userRegistered: false,
};
// !!! This is the important part !!!
// Register user with ChainLetter, get a token, then send the token to the client
// This doesn't need to be in the login route, it can be in any other server route
if ((await RegisterUser(currentUser)) === false) {
return res.send({ message: "User Registration Failed." });
}
const token = await GetUserToken(currentUser);
if (!token) {
return res.send({ message: "Token Request Failed." });
}
// By embedding the token in the form, the client can submit the form to ChainLetter via POST, allowing the cookie to be set
return res.send({
message: "Logged in",
form: `
<form action="${
process.env.CL_APIURL
}/api/v1/client/post_user_token" method="POST">
<div class="form-group">
<input name="authorization" type="hidden" value="AUTH ${token}" />
<input name="return_url" type="hidden" value="${
req.protocol
}://${req.get("host")}" />
<input type="submit" value="Access ChainLetter" />
</div>
</form>
`,
});
}
res.send({ message: "Incorrect Login or Password" });
});
// Serve static files
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "/index.html"));
});
app.get("/style.css", (req, res) => {
res.sendFile(path.join(__dirname, "/style.css"));
});
// Start the server
app.listen(3111, "0.0.0.0", () => {
console.log(`Listening to requests on http://localhost:3111`);
});
Add an index.html to activate the nodejs API:
<!DOCTYPE html>
<html>
<head>
<title>Login Form</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- Simple Login Form Example, nothing custom here -->
<div class="container" id="logincontainer">
<h2>"Your Site" Login Form</h2>
<p style="text-align: center;">
This is an example login form that will log you in to "Your Site" with a normal user account. <br />
There is nothing custom here, it's just the normal login form that you would use to log in to your site.
</p>
<br />
<br />
<strong>Username:</strong> johndoe123
<br />
<strong>Password:</strong> password
<br />
<br />
<div class="form-group">
<label for="username">Username:</label>
<input type="text" id="username" placeholder="Enter your username" value="johndoe123">
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" placeholder="Enter your password" value="password">
</div>
<div class="form-group">
<button onclick="login()">Login</button>
</div>
<div id="result" class="result"></div>
</div>
<div class="container start-hidden" id="formcontainer">
<h2>Logged In to "Your Site"</h2>
<p style="text-align: center;">Clicking this button will log you in to ChainLetter with a linked account.</p>
</div>
<script>
let debounce = false;
function login() {
if (debounce) {
return;
}
debounce = true;
var username = document.getElementById("username").value;
var password = document.getElementById("password").value;
// Make a GET request to our own site's login function and hopefully get a form back
fetch("/login", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username: username,
password: password
})
})
.then(response => response.json())
.then(data => {
// Replace the content of the result div with the API response
document.getElementById("result").innerHTML = data.message;
if (data.form) {
document.getElementById("logincontainer").classList.add("fade-out");
document.getElementById("formcontainer").classList.add("fade-in");
document.getElementById("formcontainer").classList.remove("start-hidden");
// Add the form to the page
document.getElementById("formcontainer").innerHTML = document.getElementById("formcontainer").innerHTML + data.form;
} else {
debounce = false;
}
})
.catch(error => {
console.error(error);
document.getElementById("result").innerHTML = "An error occurred.";
debounce = false;
});
}
</script>
</body>
</html>
And a simple style.css…
body {
font-family: Arial, sans-serif;
background-color: #f2f2f2;
}
.container {
max-width: 400px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
max-height: 100rem;
margin-top: 5rem;
overflow: hidden;
}
.start-hidden {
display: none;
}
.container h2 {
text-align: center;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
.form-group input {
width: 23.5rem;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.form-group button {
width: 100%;
padding: 10px;
background-color: #4caf50;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
}
.result {
margin-top: 20px;
padding: 10px;
background-color: #f2f2f2;
border-radius: 5px;
}
.fade-out {
animation: fadeOut 0.5s ease-in-out forwards;
overflow: hidden;
}
@keyframes fadeOut {
0% {
opacity: 1;
}
99% {
max-height: 0px;
opacity: 0;
}
100% {
max-height: 0px;
display: none;
}
}
.fade-in {
animation: fadeIn 2s ease-in-out forwards;
overflow: hidden;
}
@keyframes fadeIn {
0% {
opacity: 0;
max-height: 0px;
display: none;
}
1% {
opacity: 0;
max-height: 0px;
display: block;
}
100% {
opacity: 1;
max-height: 100rem;
display: block;
}
}
Step 7: Running the Application in Production
Use a process manager like pm2 to run and manage your Node.js application:
npm install -g pm2 pm2 start app.js --name "chainletter-login"
Or, during development, you can use nodemon for automatic restarts on changes:
npm install -g nodemon nodemon app.js
This starts the local Node.js server, making your application available at http://localhost:3111. Now you can easily test the functionality of your login button.
Security Tip: Keep your Node.js server up-to-date and use tools like pm2 to handle process management securely. It can help with load balancing, automatic restarts, and keeping your app alive indefinitely.
By following this guide and implementing the highlighted security measures, you can safely add a ChainLetter login button to your website. Happy coding!
