Appearance
Single Sign-On (SSO)
Comnto supports seamless Single Sign-On (SSO), allowing your existing users to comment without creating new accounts. Your backend issues a short HMAC-signed token for each authenticated user, and the embedded widget sends that token to Comnto’s API to log the user in and receive normal access and refresh tokens — just like a regular login.
Requirements
- Enable SSO (and optionally Login button visibility) in the dashboard.
- You need the site’s
sso_secret. Keep it on the server—never ship it to the browser. - Tokens must be generated on demand, over HTTPS, and should expire within a few minutes.
Payload & signature
Build a JSON payload with the following fields:
| Field | Description |
|---|---|
site | Your Comnto site id. Must match the site-id in the embed. |
user_id | Your internal user identifier (string or number). Reuse the same value for the same person. |
email | Optional but recommended. Lowercase string. |
name | Display name shown next to comments. |
username | Optional preferred username. Comnto picks a unique alternative if the value is taken. |
avatar | Optional external avatar URL. Comnto queues an import after the first login. |
iat | Issued-at timestamp (Unix seconds). |
exp | Expiration timestamp (Unix seconds). Keep the window short (e.g. exp = iat + 600). |
Sign the payload like this:
token = base64url( JSON payload ) + '.' + hex( HMAC_SHA256(base64url payload, sso_secret) )Comnto validates the signature, checks iat/exp (±5 minute leeway), ensures site matches, then either creates or updates the user.
Token generation examples
js
import crypto from 'node:crypto';
const base64url = (input) =>
Buffer.from(input)
.toString('base64')
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
export function makeComntoSsoToken({ siteId, userId, email, name, username, avatar, secret }) {
const now = Math.floor(Date.now() / 1000);
const payload = {
site: siteId,
user_id: String(userId),
email,
name,
username,
avatar,
iat: now,
exp: now + 600,
};
const body = base64url(JSON.stringify(payload));
const signature = crypto.createHmac('sha256', secret).update(body).digest('hex');
return `${body}.${signature}`;
}php
<?php
function base64url($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
function make_sso_token($siteId, $userId, $email, $name, $username, $avatar, $secret) {
$now = time();
$payload = json_encode([
'site' => (string)$siteId,
'user_id' => (string)$userId,
'email' => $email,
'name' => $name,
'username' => $username,
'avatar' => $avatar,
'iat' => $now,
'exp' => $now + 600
]);
$body = base64url($payload);
$sig = hash_hmac('sha256', $body, $secret);
return $body . '.' . $sig;
}python
import base64, hmac, hashlib, json, time
def base64url(b: bytes) -> str:
return base64.urlsafe_b64encode(b).decode().rstrip('=')
def make_sso_token(site_id: str, user_id: str, email: str, name: str, username: str, avatar: str, secret: str) -> str:
now = int(time.time())
payload = {
'site': site_id,
'user_id': str(user_id),
'email': email,
'name': name,
'username': username,
'avatar': avatar,
'iat': now,
'exp': now + 600,
}
body = base64url(json.dumps(payload).encode())
sig = hmac.new(secret.encode(), body.encode(), hashlib.sha256).hexdigest()
return f"{body}.{sig}"rb
require 'json'
require 'openssl'
require 'base64'
def base64url(str)
Base64.urlsafe_encode64(str).gsub('=', '')
end
def make_sso_token(site_id:, user_id:, email:, name:, username:, avatar:, secret:)
now = Time.now.to_i
payload = {
site: site_id,
user_id: user_id.to_s,
email: email,
name: name,
username: username,
avatar: avatar,
iat: now,
exp: now + 600
}
body = base64url(payload.to_json)
sig = OpenSSL::HMAC.hexdigest('SHA256', secret, body)
"#{body}.#{sig}"
endgo
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"time"
)
func base64url(b []byte) string {
return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(b)
}
func makeSSOToken(siteId, userID, email, name, username, avatar, secret string) (string, error) {
now := time.Now().Unix()
payload := map[string]any{
"site": siteId,
"user_id": userID,
"email": email,
"name": name,
"username": username,
"avatar": avatar,
"iat": now,
"exp": now + 600,
}
bodyBytes, _ := json.Marshal(payload)
body := base64url(bodyBytes)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(body))
sig := hex.EncodeToString(mac.Sum(nil))
return fmt.Sprintf("%s.%s", body, sig), nil
}Delivering the token to the widget
The token must only be produced on the server and delivered to the page over HTTPS.
Option 1 — Inline attribute
html
<div
id="comments"
data-comnto
data-site="my-blog"
data-sso="{{SSO_TOKEN_FROM_SERVER}}"
data-login-button-visible
></div>
<script src="https://comnto.com/embed.js" defer></script>Option 2 — Programmatic call
html
<div id="comments"></div>
<script src="https://comnto.com/embed.js" defer></script>
<script>
window.addEventListener('DOMContentLoaded', async () => {
const token = await fetch('/comnto/sso-token', { credentials: 'include' }).then((res) => res.text());
window.comnto({
el: '#comments',
site: 'my-blog',
sso: token,
login_button_visible: true,
});
});
</script>Refreshing / logging out
- Issue short tokens (5–10 minutes). When you produce a new one, call
widget.reload({ sso: freshToken }). - Send
widget.reload({ sso: '' })to revoke the session. - The widget automatically POSTs the token to
POST /v1/{site_id}/sso, receives a JWT + refresh token, and stores them internally. You do not need to call that endpoint yourself.
SSO-only mode & login button
Enabling SSO-only mode from the dashboard prevents normal login through Comnto’s built-in form. Keep login_button_visible on if you still want the UI to show a “Login” action. Listen for the comnto-login event to launch your own authentication window:
js
window.addEventListener('comnto-login', () => {
openMyLoginModal(); // trigger your auth flow, then refresh the SSO token
});Once your flow succeeds, fetch a new token and reload the widget or respond to the comnto:auth message that the widget broadcasts.
Security checklist
- Always generate tokens on the server; never expose the
sso_secret. - Use short-lived tokens (iat / exp) to keep sessions limited in time.
With these steps in place the embed signs users in using your identity provider while Comnto handles comments, moderation, and notifications.