Skip to main content

Channel App Auth Flow

If your application requires authentication to access resources and you want to leverage the existing user ecosystem within Mezon communities, Channel App provides an advanced feature for developers to integrate authentication and authorization of channel users through Mezon's user system.

Channel App offers a powerful authentication mechanism that allows seamless integration between your web application and Mezon's user base, eliminating the need for separate user management systems.

What is Hash-Based Authentication?

Hash-based authentication is a secure method where:

  • User authentication data is embedded in a cryptographically signed hash string by the Mezon platform
  • The hash contains user information and is validated using HMAC-SHA256 signatures
  • Your web application validates the hash signature to authenticate users
  • No external OAuth2 redirects are required

Architecture Overview

By following the Getting Started Guide, you now have a channel app on the Mezon platform with your web application integrated. Let's explore the relationship between the different components in the authentication flow.

Hash Data Structure

The hash data received from Mezon contains the following components:

Raw Hash Format

query_id=AAHdF6UqAAAAAB0XpSoKhRAd&user=%7B%22id%22%3A123456789%2C%22username%22%3A%22mezon_dev%22%2C%22mezon_id%22%3A%22mezon.dev%40ncc.asia%22%7D&auth_date=1640995200&signature=abc123def456&hash=7f3c4e8a9b2d1f6e5c8a7b4e9d2f8c1e6a9b3c7d

Decoded Parameters

  • query_id: Unique query identifier from Mezon
  • user: URL-encoded JSON string containing user information
  • auth_date: Unix timestamp of when the authentication was created
  • signature: Additional signature data for verification
  • hash: HMAC-SHA256 signature of all the data (used for validation)

User Object Structure

{
"id": 123456789,
"username": "mezon_dev",
"display_name": "Mezon Dev",
"avatar_url": "https://cdn.mezon.ai/avatar.jpg",
"mezon_id": "mezon.dev@ncc.asia"
}

How to Implement Hash Authentication Flow?

Step 1: Frontend Implementation

When Mezon opens your channel app, it passes user authentication data via the URL query parameter. Extract this data and send it to your backend for validation.

Extract Authentication Data from URL

// Extract authentication data from URL query parameters
function getAuthDataFromURL() {
const urlParams = new URLSearchParams(window.location.search);
const authData = urlParams.get('data');

if (!authData) {
console.warn('No authentication data found in URL');
return null;
}

// Decode the URL-encoded data
return decodeURIComponent(authData);
}

// Get the authentication data
const rawHashData = getAuthDataFromURL();

if (rawHashData) {
console.log('Authentication data received:', rawHashData);
// Process the hash data for authentication
authenticateWithHash(rawHashData);
} else {
console.error('No authentication data available');
// Handle the case where no auth data is present
}

Complete Frontend Implementation

Here's a comprehensive HTML and JavaScript implementation for handling Mezon authentication via URL parameters:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mezon Channel App Authentication</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background: var(--mezon-bg-color, #f5f5f5);
color: var(--mezon-text-color, #333);
}

.auth-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
background: var(--mezon-card-bg, #ffffff);
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.status {
padding: 10px;
border-radius: 5px;
margin: 10px 0;
}

.status.loading {
background-color: #fff3cd;
color: #856404;
}
.status.success {
background-color: #d4edda;
color: #155724;
}
.status.error {
background-color: #f8d7da;
color: #721c24;
}
.status.info {
background-color: #d1ecf1;
color: #0c5460;
}

.auth-details {
background: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin: 15px 0;
font-family: monospace;
font-size: 12px;
white-space: pre-wrap;
}

button {
background: var(--mezon-button-bg, #007bff);
color: var(--mezon-button-text, white);
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin: 5px;
}

button:hover {
background: var(--mezon-button-hover, #0056b3);
}
</style>
</head>
<body>
<div class="auth-container">
<h1>Mezon Channel App Authentication</h1>

<div id="connectionStatus" class="status info">Initializing...</div>

<div id="authSection">
<h3>Authentication Status</h3>
<div id="authStatus" class="status info">Ready to authenticate</div>
</div>

<div id="userInfo" style="display: none;">
<h3>Authenticated User</h3>
<div id="userDetails" class="auth-details"></div>
</div>
</div>

<script>
class MezonAuthApp {
constructor() {
this.authEndpoint = "/api/auth/mezon-hash"; // Your backend auth endpoint
this.init();
}

init() {
this.updateConnectionStatus();
this.checkForAuthData();
}

updateConnectionStatus() {
const statusEl = document.getElementById("connectionStatus");
statusEl.className = "status success";
statusEl.innerHTML = "✅ Channel App Loaded";
}

checkForAuthData() {
// Extract authentication data from URL
const authData = this.getAuthDataFromURL();

if (authData) {
this.updateAuthStatus("Authentication data found", "info");
this.authenticateWithHash(authData);
} else {
this.updateAuthStatus("No authentication data found in URL", "info");
}
}

getAuthDataFromURL() {
const urlParams = new URLSearchParams(window.location.search);
const authData = urlParams.get('data');

if (!authData) {
console.warn('No authentication data found in URL');
return null;
}

try {
// Decode the URL-encoded data
const decodedData = decodeURIComponent(authData);
console.log('Authentication data received');
return decodedData;
} catch (error) {
console.error('Error decoding authentication data:', error);
return null;
}
}

authenticateWithHash(rawHashData) {
console.log("Processing hash data for authentication");
this.updateAuthStatus("Processing authentication...", "loading");

try {
// Process hash data
const authModel = this.processHashData(rawHashData);

// Send to backend for validation
this.sendAuthRequest(authModel);
} catch (error) {
console.error("Error processing hash data:", error);
this.updateAuthStatus(
"Error processing authentication data",
"error"
);
}
}

processHashData(rawHashData) {
// Base64 encode the hash data for secure transmission
const encodedHashData = btoa(rawHashData);

return {
hashData: encodedHashData,
};
}

async sendAuthRequest(authModel) {
try {
const response = await fetch(this.authEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(authModel),
});

if (!response.ok) {
throw new Error(`Authentication failed: ${response.statusText}`);
}

const result = await response.json();
this.handleAuthSuccess(result);
} catch (error) {
console.error("Authentication request failed:", error);
this.updateAuthStatus(
`Authentication failed: ${error.message}`,
"error"
);
}
}

handleAuthSuccess(result) {
console.log("Authentication successful:", result);
this.updateAuthStatus("Authentication successful!", "success");

// Store the authentication token
if (result.token) {
localStorage.setItem("auth_token", result.token);
}

// Display user information
if (result.user) {
this.displayUserInfo(result.user);
}

// Redirect or continue with app initialization
// window.location.href = '/dashboard';
}

displayUserInfo(user) {
const userInfoDiv = document.getElementById("userInfo");
const userDetailsDiv = document.getElementById("userDetails");

userDetailsDiv.textContent = JSON.stringify(user, null, 2);
userInfoDiv.style.display = "block";
}

updateAuthStatus(message, type) {
const authStatusEl = document.getElementById("authStatus");
authStatusEl.className = `status ${type}`;
authStatusEl.textContent = message;
}
}

// Initialize the app when DOM is ready
const mezonAuthApp = new MezonAuthApp();
</script>
</body>
</html>

Simplified Authentication Handler

For integration into existing applications, here's a simplified JavaScript class:

class SimpleMezonAuth {
constructor(options = {}) {
this.authEndpoint = options.authEndpoint || "/api/auth/mezon-hash";
this.onSuccess = options.onSuccess || this.defaultSuccessHandler;
this.onError = options.onError || this.defaultErrorHandler;

this.init();
}

init() {
this.startAuthFlow();
}

startAuthFlow() {
// Extract auth data from URL
const authData = this.getAuthDataFromURL();

if (authData) {
this.processAuthentication(authData);
} else {
this.onError('No authentication data found in URL');
}
}

getAuthDataFromURL() {
const urlParams = new URLSearchParams(window.location.search);
const authData = urlParams.get('data');

if (!authData) {
return null;
}

try {
return decodeURIComponent(authData);
} catch (error) {
console.error('Error decoding auth data:', error);
return null;
}
}

async processAuthentication(rawHashData) {
try {
const authData = {
hashData: btoa(rawHashData),
};

const response = await fetch(this.authEndpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(authData),
});

const result = await response.json();

if (result.success) {
this.onSuccess(result);
} else {
this.onError(result.error);
}
} catch (error) {
this.onError(error.message);
}
}

defaultSuccessHandler(result) {
console.log("Authentication successful:", result);
if (result.token) {
localStorage.setItem("auth_token", result.token);
}
}

defaultErrorHandler(error) {
console.error("Authentication failed:", error);
}
}

// Usage example:
// const auth = new SimpleMezonAuth({
// authEndpoint: '/api/auth/mezon-hash',
// onSuccess: (result) => {
// console.log('User authenticated:', result.user);
// window.location.href = '/dashboard';
// },
// onError: (error) => {
// alert('Authentication failed: ' + error);
// }
// });

Step 2: Backend Implementation

On your server side, you need to define an endpoint that accepts the user hash information and validates it.

Hash Validation Process

The hash validation process involves understanding the mechanism and data that Mezon uses. The hash token creation process includes 3 steps:

  1. MD5 Hash: Your App Secret is hashed using MD5, the result is used as the key for step 2
  2. HMAC-SHA256: Use HMAC-SHA256 with the key from step 1 to hash the string "WebAppData", the result is used in step 3
  3. Final Hash: Use all data sent from the frontend (except the hash part) as input for step 3, combined with the result from step 2 as the key. Hash all data using HMAC-SHA256 to get the final hash string
const crypto = require('crypto');

function validateMezonHash(appSecret, hashData) {
try {
// Parse hash data
const delimiter = '&hash=';
const index = hashData.indexOf(delimiter);
const queryData = hashData.substring(0, index);
const receivedHash = hashData.substring(index + delimiter.length);

// Step 1: MD5 hash of app secret
const hashedSecret = crypto.createHash('md5').update(appSecret).digest('hex');

// Step 2: HMAC-SHA256 of "WebAppData" with hashed secret
const secretKey = crypto.createHmac('sha256', hashedSecret).update('WebAppData').digest();

// Step 3: HMAC-SHA256 of query data with secret key
const computedHash = crypto.createHmac('sha256', secretKey).update(queryData).digest('hex');

// Compare hashes
return computedHash === receivedHash;
} catch (error) {
console.error('Hash validation error:', error);
return false;
}
}

// Usage example
app.post('/api/auth/mezon-hash', (req, res) => {
const { hashData } = req.body;
const rawHashData = Buffer.from(hashData, 'base64').toString('utf-8');

const isValid = validateMezonHash(process.env.MEZON_APP_SECRET, rawHashData);

if (isValid) {
// Extract user data and create session
const userData = parseUserData(rawHashData);
const token = generateJWTToken(userData);

res.json({
success: true,
accessToken: token,
user: userData
});
} else {
res.status(401).json({
success: false,
error: 'Invalid hash signature'
});
}
});

Complete Backend Implementation Example (Node.js/Express)

const express = require("express");
const crypto = require("crypto");
const jwt = require("jsonwebtoken");
const app = express();

app.use(express.json());

// Utility functions
function validateMezonHash(appSecret, hashData) {
try {
const delimiter = "&hash=";
const index = hashData.indexOf(delimiter);
const queryData = hashData.substring(0, index);
const receivedHash = hashData.substring(index + delimiter.length);

// Hash validation process
const hashedSecret = crypto
.createHash("md5")
.update(appSecret)
.digest("hex");
const secretKey = crypto
.createHmac("sha256", hashedSecret)
.update("WebAppData")
.digest();
const computedHash = crypto
.createHmac("sha256", secretKey)
.update(queryData)
.digest("hex");

return computedHash === receivedHash;
} catch (error) {
console.error("Hash validation error:", error);
return false;
}
}

function parseUserData(hashData) {
const delimiter = "&hash=";
const queryData = hashData.substring(0, hashData.indexOf(delimiter));

const params = new URLSearchParams(queryData);
const userJSON = decodeURIComponent(params.get("user"));
const user = JSON.parse(userJSON);

return {
query_id: params.get("query_id"),
user: user,
auth_date: parseInt(params.get("auth_date")),
signature: params.get("signature"),
};
}

function generateJWTToken(userData) {
const payload = {
user_id: userData.user.id,
username: userData.user.username,
mezon_id: userData.user.mezon_id,
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24, // 24 hours
};

return jwt.sign(payload, process.env.JWT_SECRET);
}

// Authentication endpoint
app.post("/api/auth/mezon-hash", async (req, res) => {
try {
const { hashData } = req.body;

if (!hashData) {
return res.status(400).json({
success: false,
error: "Hash data is required",
});
}

// Decode base64 hash data
const rawHashData = Buffer.from(hashData, "base64").toString("utf-8");
console.log("Received hash data:", rawHashData);

// Validate hash signature
const isValid = validateMezonHash(
process.env.MEZON_APP_SECRET,
rawHashData
);
if (!isValid) {
return res.status(401).json({
success: false,
error: "Invalid hash signature",
});
}

// Parse user data
const userData = parseUserData(rawHashData);
console.log("Parsed user data:", userData);

// Check if user exists in your database
const user = await findOrCreateUser(userData.user);
if (!user) {
return res.status(401).json({
success: false,
error: "User not found or cannot be created",
});
}

// Generate JWT token
const accessToken = generateJWTToken(userData);

// Return successful authentication
res.json({
success: true,
accessToken: accessToken,
expiresIn: 86400, // 24 hours in seconds
user: {
id: user.id,
username: user.username,
email: user.email,
displayName: user.displayName,
},
});
} catch (error) {
console.error("Authentication error:", error);
res.status(500).json({
success: false,
error: "Internal server error",
});
}
});

// Mock user database operations
async function findOrCreateUser(mezonUser) {
// Implement your user lookup/creation logic here
// This could involve database queries to find existing users
// or create new users based on Mezon data

// Example implementation:
let user = await User.findOne({ email: mezonUser.mezon_id });

if (!user) {
// Create new user if doesn't exist
user = await User.create({
email: mezonUser.mezon_id,
username: mezonUser.username,
displayName: mezonUser.display_name || mezonUser.username,
avatarUrl: mezonUser.avatar_url,
mezonId: mezonUser.id,
});
}

return user;
}

app.listen(3000, () => {
console.log("Server running on port 3000");
});

Complete Authentication Flow

The complete flow works as follows:

  1. User opens channel app in Mezon
  2. Mezon generates hash with user data and cryptographic signature
  3. Mezon loads your web application with authentication data in URL query parameter (?data=...)
  4. Frontend extracts auth data: JavaScript reads the data parameter from URL
  5. Data decoding: Frontend URL-decodes the authentication string
  6. Hash processing: Frontend base64 encodes hash and sends to backend
  7. Hash validation: Backend validates signature using cryptographic verification
  8. User authentication: Backend finds/creates user and generates JWT token
  9. Session establishment: Frontend stores token and redirects to application

Error Handling and Troubleshooting

Common Error Scenarios

Frontend Error Handling

// Comprehensive error handling in your service
public authenticateWithHash(authModel: MezonHashAuthModel): Observable<any> {
return this.http.post('/api/auth/mezon-hash', authModel).pipe(
map(response => ({ ...response, loading: false })),
catchError((error: HttpErrorResponse) => {
console.error('Authentication failed:', error);

let errorMessage = 'Authentication failed';
if (error.error?.error) {
errorMessage = error.error.error;
} else if (error.status === 401) {
errorMessage = 'Invalid authentication credentials';
} else if (error.status === 0) {
errorMessage = 'Network error - please check your connection';
}

return of({
loading: false,
success: false,
error: errorMessage
});
})
);
}

Backend Error Handling

app.post("/api/auth/mezon-hash", async (req, res) => {
try {
// ... authentication logic
} catch (error) {
console.error("Authentication error:", error);

// Return appropriate error responses
if (error.name === "ValidationError") {
return res.status(400).json({
success: false,
error: "Invalid request data",
});
} else if (error.name === "JsonWebTokenError") {
return res.status(500).json({
success: false,
error: "Token generation failed",
});
} else {
return res.status(500).json({
success: false,
error: "Internal server error",
});
}
}
});

Security Best Practices

  1. Always validate hash signatures before processing user data
  2. Use environment variables for sensitive configuration like app secrets
  3. Implement proper error handling without exposing internal details
  4. Add rate limiting to prevent abuse of authentication endpoints
  5. Log authentication attempts for security monitoring
  6. Validate user data before creating database records
  7. Use HTTPS for all communications in production

Testing Your Implementation

Backend Testing

// Test hash validation
const assert = require("assert");

function testHashValidation() {
const appSecret = "test-secret";
const validHashData =
"query_id=test&user=%7B%22id%22%3A123%7D&auth_date=1640995200&hash=valid-hash";

// Test with valid hash
const isValid = validateMezonHash(appSecret, validHashData);
console.log("Hash validation test:", isValid ? "PASSED" : "FAILED");
}

testHashValidation();

This completes the comprehensive Channel App Authentication Flow documentation. The hash-based authentication provides a secure and seamless way to authenticate users from Mezon into your application without requiring separate login flows.