Tokens
Ein Hivesigner-Token ist eine kurze signierte Aussage. Sie nennt ein Hive-Konto, die App, für die sie erstellt wurde, und den Zeitpunkt der Signatur. Ihr Server kann ein Token über die API oder selbst prüfen. Diese Seite zeigt, was ein Token enthält, wie lange es gilt und beide Wege der Prüfung.
Wie ein Token aussieht
Ein Token ist ein JSON-Objekt, kodiert in base64url, mit einem Unterschied zum üblichen base64url: Als Füllzeichen dient . statt =. Verglichen mit einfachem base64 wird also aus + ein -, aus / ein _ und aus = ein .. Jedes Token beginnt mit eyJzaWduZWRfbWVzc2FnZSI6.
Dekodiert sieht ein Zugriffstoken aus dem Token-Flow so aus:
{
"signed_message": { "type": "posting", "app": "myapp" },
"authors": ["alice"],
"timestamp": 1789819200,
"signatures": ["1f5a0c...e27b"],
"authority": "posting"
}
| Feld | Bedeutung |
|---|---|
signed_message.type |
Was das Token ist: login, posting, code oder refresh. Siehe Arten von Tokens. |
signed_message.app |
Das App-Konto, für das das Token erstellt wurde. Ein Anmeldetoken einer Website ohne App-Konto hat keines. |
authors[0] |
Das Hive-Konto, für das das Token gilt. |
timestamp |
Wann es signiert wurde, in Sekunden seit dem 1970-01-01 UTC. |
signatures[0] |
Die Signatur, als Hex-Zeichenkette. |
authority |
Nur in Tokens, die im Browser signiert wurden: welcher Schlüssel der Person signiert hat, posting oder active. Dieses Feld liegt außerhalb der signierten Daten. Um den signierenden Schlüssel zu kennen, gewinnen Sie ihn aus der Signatur zurück. |
Die Signatur ist eine secp256k1-Signatur über den sha256-Hash von JSON.stringify({ signed_message, authors, timestamp }), mit den Schlüsseln in dieser Reihenfolge.
Ein Token dekodieren
In Node.js:
export function decodeToken(token) {
const base64 = token.replace(/[-_.]/g, (c) => ({ '-': '+', _: '/', '.': '=' })[c]);
return JSON.parse(Buffer.from(base64, 'base64').toString('utf8'));
}
In einem Browser:
export function decodeToken(token) {
const base64 = token.replace(/[-_.]/g, (c) => ({ '-': '+', _: '/', '.': '=' })[c]);
const bytes = Uint8Array.from(atob(base64), (ch) => ch.charCodeAt(0));
return JSON.parse(new TextDecoder().decode(bytes));
}
Dekodieren ist kein Prüfen. Jeder kann eine Zeichenkette bauen, die zu dieser Form dekodiert. Prüfen Sie ein Token, bevor Sie ihm vertrauen.
Arten von Tokens
| Token | type |
app |
Signiert von | Woher Sie es bekommen |
|---|---|---|---|---|
| Zugriffstoken, Token-Flow | posting |
Ihre App | Dem Posting-Schlüssel der Person oder ihrem Active-Schlüssel, wenn Hivesigner keinen Posting-Schlüssel für das Konto hat | access_token an Ihrem Callback |
Anmeldetoken, scope=login |
login |
Ihre App | Dem Posting- oder Active-Schlüssel der Person | access_token an Ihrem Callback |
| Anmeldetoken, Website ohne App-Konto | login |
Keines | Dem Posting- oder Active-Schlüssel der Person | access_token an Ihrem Callback |
| Code | code |
Ihre App | Dem Posting- oder Active-Schlüssel der Person | code an Ihrem Callback |
| Zugriffstoken, Code-Flow | posting |
Ihre App | Dem Posting-Schlüssel von @hivesigner | /api/oauth2/token |
| Refresh-Token | refresh |
Ihre App | Dem Posting-Schlüssel von @hivesigner | /api/oauth2/token |
Ein Code und ein Refresh-Token sind keine Zugriffstokens. Akzeptieren Sie keines von beiden je als Anmeldung.
Wie lange ein Token gilt
Ein Zugriffstoken gilt 7 Tage: expires_in ist 604800 Sekunden, gezählt ab seinem timestamp. Wenn es abgelaufen ist:
- Token-Flow: Schicken Sie die Person erneut zur Anmeldung. Wer Ihre App bereits autorisiert hat, sieht "Anmelden bei APP" und braucht einen Klick.
- Code-Flow: Ihr Server holt sich mit dem Refresh-Token und Ihrem Client-Secret ein neues Zugriffstoken. Siehe Erneuern.
Behandeln Sie ein Token als abgelaufen, sobald sein timestamp älter als 7 Tage ist. Akzeptieren Sie ein viel geringeres Alter für alles, was Sie direkt nach der Weiterleitung prüfen. Tauschen Sie einen Code sofort. Akzeptieren Sie ein Anmeldetoken nur innerhalb weniger Minuten nach seinem timestamp.
Ein Token auf Ihrem Server prüfen
Bevor Ihr Server einem Token vertraut, das ein Browser oder eine App ihm sendet, prüfen Sie, dass:
- das Konto oder @hivesigner es wirklich signiert hat;
- es für Ihre App erstellt wurde;
- es die Art Token ist, die Sie erwarten;
- es jung genug ist.
Die API fragen
Rufen Sie /api/me mit dem Token auf. Ein gültiges Token liefert das Konto in user:
curl https://hivesigner.com/api/me -H 'Authorization: ACCESS_TOKEN'
Ein ungültiges Token liefert 401 mit invalid_grant. Siehe GET /api/me.
/api/me bestätigt die Signatur. Die Antwort nennt nicht die App, für die das Token erstellt wurde. Dekodieren Sie das Token also zusätzlich und prüfen Sie app, type und Alter selbst. Ein Token, das für eine andere App erstellt wurde, darf niemanden bei Ihnen anmelden.
import { decodeToken } from './decode-token.js';
const WEEK = 7 * 24 * 60 * 60;
// type: 'posting' for an access token, 'login' for a scope=login sign-in.
export async function hivesignerUser(token, { app, type }) {
const res = await fetch('https://hivesigner.com/api/me', {
headers: { Authorization: token },
});
if (!res.ok) return null;
const me = await res.json();
let body;
try {
body = decodeToken(token);
} catch {
return null;
}
const { signed_message, timestamp } = body ?? {};
if (signed_message?.type !== type || signed_message.app !== app) return null;
const age = Math.floor(Date.now() / 1000) - timestamp;
if (!(age >= -60 && age <= WEEK)) return null;
return me.user;
}
Die API akzeptiert nur Tokens, die eine App nennen. Prüfen Sie ein Anmeldetoken einer Website ohne App-Konto selbst.
Selbst prüfen
- Dekodieren Sie das Token.
- Prüfen Sie, dass
signed_message.typedie erwartete Art ist:postingfür ein Zugriffstoken,loginfür ein Anmeldetoken. - Prüfen Sie, dass
signed_message.appdas Konto Ihrer App ist. Für eine Website ohne App-Konto prüfen Sie, dass keines vorhanden ist. - Prüfen Sie das Alter anhand von
timestamp. - Berechnen Sie den sha256-Hash von
JSON.stringify({ signed_message, authors, timestamp }). - Gewinnen Sie den öffentlichen Schlüssel aus
signatures[0]und diesem Hash zurück. - Lesen Sie das Konto
authors[0]jetzt aus der Hive-Blockchain, denn Menschen können ihre Schlüssel wechseln. Der zurückgewonnene Schlüssel muss einer seiner aktuellen Posting- oder Active-Schlüssel sein. Ein Token aus/api/oauth2/tokenist stattdessen von @hivesigner signiert: Akzeptieren Sie dafür einen aktuellen Posting-Schlüssel des Kontos @hivesigner.
In Node.js mit @ecency/sdk, das PrivateKey, PublicKey, Signature und callRPC unter @ecency/sdk/hive bereitstellt:
import { createHash } from 'node:crypto';
import { Signature, callRPC } from '@ecency/sdk/hive';
import { decodeToken } from './decode-token.js';
const WEEK = 7 * 24 * 60 * 60;
// Returns the Hive username the token is for, or null.
export async function verifyHivesignerToken(token, { type, app, maxAge = WEEK }) {
let body;
try {
body = decodeToken(token);
} catch {
return null;
}
const { signed_message, authors, timestamp, signatures } = body ?? {};
// What the token is and who it is for.
if (signed_message?.type !== type || signed_message.app !== app) return null;
const username = Array.isArray(authors) ? authors[0] : undefined;
if (typeof username !== 'string' || !Array.isArray(signatures)) return null;
// How old it is, allowing one minute of clock difference.
const age = Math.floor(Date.now() / 1000) - timestamp;
if (!Number.isInteger(timestamp) || age < -60 || age > maxAge) return null;
// Which key signed it.
const digest = createHash('sha256')
.update(JSON.stringify({ signed_message, authors, timestamp }))
.digest();
let signer;
try {
signer = Signature.from(signatures[0]).getPublicKey(digest).toString();
} catch {
return null;
}
// Whether that key belongs to the account now.
const accounts = await callRPC('condenser_api.get_accounts', [[username, 'hivesigner']]);
const user = accounts.find((a) => a.name === username);
if (!user) return null;
const keys = [...user.posting.key_auths, ...user.active.key_auths];
if (type === 'posting') {
// Access tokens from /api/oauth2/token are signed by @hivesigner.
const hivesigner = accounts.find((a) => a.name === 'hivesigner');
keys.push(...(hivesigner?.posting.key_auths ?? []));
}
return keys.some(([key]) => key === signer) ? username : null;
}
So nutzen Sie es:
// An access token from the token flow:
const user = await verifyHivesignerToken(token, { type: 'posting', app: 'myapp' });
// A sign-in token from a site with no app account, right after the redirect:
const visitor = await verifyHivesignerToken(token, { type: 'login', app: undefined, maxAge: 300 });
Die Bibliothek dhive (@hiveio/dhive) geht auch: Berechnen Sie den Hash mit cryptoUtils.sha256(message) und gewinnen Sie den Schlüssel mit Signature.fromString(signatures[0]).recover(digest).toString() zurück.
Tokens sicher aufbewahren
Wer ein Posting-Token besitzt, kann bis zu dessen Ablauf über Ihre App als die Person übertragen. Behandeln Sie es wie ein Passwort.
- Bewahren Sie Tokens auf Ihrem Server auf oder in einem httpOnly-, Secure-Cookie. Refresh-Tokens und Ihr Client-Secret gehören ausschließlich auf den Server.
- Legen Sie ein Token nie in eine URL, die Sie protokollieren. Der Token-Flow liefert das Token im Query-String Ihres Callbacks. Lesen Sie es auf Ihrem Server und leiten Sie dann auf eine URL ohne Token weiter. Lassen Sie den Query-String des Callbacks aus Ihren Protokollen heraus.
- Laden Sie auf Ihrer Callback-Seite nichts von anderen Websites, damit die Adresse mit dem Token nicht an sie geht. Ein Header
Referrer-Policy: no-referrerauf dieser Seite hilft. - Senden Sie ein Token nur an Ihren eigenen Server und an
https://hivesigner.com/api/.
Abmelden und Zugriff entziehen
- Eine Person abmelden heißt, das Token zu verwerfen: Löschen Sie es aus Ihrer Sitzung oder Ihrem Cookie. Sie können zusätzlich
/api/oauth2/token/revokeaufrufen, um Hivesigner mitzuteilen, dass die Person sich abgemeldet hat. Ihre App verwirft das Token ohnehin selbst. - Den Zugriff Ihrer App dauerhaft zu beenden, ist die Entscheidung der Person. Unter https://hivesigner.com/authorized-apps oder unter
https://hivesigner.com/revoke/APPentfernt sie das Konto Ihrer App on-chain aus ihrer Posting-Berechtigung. Danach überträgt die API nicht mehr über Ihre App für sie.