Tokeny
Token Hivesigner to krótkie podpisane oświadczenie. Wskazuje konto Hive, aplikację, dla której powstał, i czas podpisania. Twój serwer może sprawdzić token przez API albo samodzielnie. Ta strona pokazuje, co zawiera token, jak długo działa i obie drogi sprawdzenia.
Jak wygląda token
Token to obiekt JSON zakodowany w base64url, z jedną różnicą wobec standardowego base64url: wypełnienie używa . zamiast =. W porównaniu ze zwykłym base64 + staje się -, / staje się _, a = staje się .. Każdy token zaczyna się od eyJzaWduZWRfbWVzc2FnZSI6.
Po zdekodowaniu token dostępu z przepływu z tokenem wygląda tak:
{
"signed_message": { "type": "posting", "app": "myapp" },
"authors": ["alice"],
"timestamp": 1789819200,
"signatures": ["1f5a0c...e27b"],
"authority": "posting"
}
| Pole | Znaczenie |
|---|---|
signed_message.type |
Czym jest token: login, posting, code albo refresh. Zobacz Rodzaje tokenów. |
signed_message.app |
Konto aplikacji, dla której token powstał. Token logowania dla witryny bez konta aplikacji go nie ma. |
authors[0] |
Konto Hive, którego token dotyczy. |
timestamp |
Czas podpisania, w sekundach od 1970-01-01 UTC. |
signatures[0] |
Podpis, jako ciąg szesnastkowy. |
authority |
Tylko w tokenach podpisanych w przeglądarce: który klucz osoby podpisał, posting czy active. To pole leży poza podpisanymi danymi. Aby wiedzieć, który klucz podpisał, odzyskaj go z podpisu. |
Podpis to podpis secp256k1 na skrócie sha256 z JSON.stringify({ signed_message, authors, timestamp }), z kluczami w tej właśnie kolejności.
Zdekoduj token
W Node.js:
export function decodeToken(token) {
const base64 = token.replace(/[-_.]/g, (c) => ({ '-': '+', _: '/', '.': '=' })[c]);
return JSON.parse(Buffer.from(base64, 'base64').toString('utf8'));
}
W przeglądarce:
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));
}
Dekodowanie to nie sprawdzanie. Każdy może zbudować ciąg, który zdekoduje się do tej postaci. Sprawdź token, zanim mu zaufasz.
Rodzaje tokenów
| Token | type |
app |
Podpisany przez | Skąd go bierzesz |
|---|---|---|---|---|
| Token dostępu, przepływ z tokenem | posting |
Twoją aplikację | Klucz publikowania osoby albo jej klucz aktywny, gdy Hivesigner nie ma klucza publikowania do tego konta | access_token na Twoim adresie zwrotnym |
Token logowania, scope=login |
login |
Twoją aplikację | Klucz publikowania albo aktywny osoby | access_token na Twoim adresie zwrotnym |
| Token logowania, witryna bez konta aplikacji | login |
Żadną | Klucz publikowania albo aktywny osoby | access_token na Twoim adresie zwrotnym |
| Kod | code |
Twoją aplikację | Klucz publikowania albo aktywny osoby | code na Twoim adresie zwrotnym |
| Token dostępu, przepływ z kodem | posting |
Twoją aplikację | Klucz publikowania konta @hivesigner | /api/oauth2/token |
| Token odświeżania | refresh |
Twoją aplikację | Klucz publikowania konta @hivesigner | /api/oauth2/token |
Kod i token odświeżania nie są tokenami dostępu. Nigdy nie przyjmuj żadnego z nich jako logowania.
Jak długo działa token
Token dostępu działa 7 dni: expires_in to 604800 sekund, liczone od jego timestamp. Po wygaśnięciu:
- Przepływ z tokenem: wyślij osobę, aby zalogowała się ponownie. Kto już autoryzował Twoją aplikację, widzi «Zaloguj się do aplikacji APP» i potrzebuje jednego kliknięcia.
- Przepływ z kodem: Twój serwer pobiera nowy token dostępu przy użyciu tokenu odświeżania i sekretu klienta. Zobacz Odświeżanie.
Uznaj token za wygasły, gdy tylko jego timestamp będzie starszy niż 7 dni. Do wszystkiego, co sprawdzasz zaraz po przekierowaniu, przyjmuj znacznie krótszy wiek. Kod wymieniaj od razu. Token logowania przyjmuj tylko w ciągu kilku minut od jego timestamp.
Sprawdź token na swoim serwerze
Zanim Twój serwer zaufa tokenowi przysłanemu przez przeglądarkę albo aplikację, sprawdź, czy:
- konto albo @hivesigner naprawdę go podpisało;
- powstał dla Twojej aplikacji;
- jest tym rodzajem tokenu, którego oczekujesz;
- jest dość świeży.
Zapytaj API
Wywołaj /api/me z tym tokenem. Prawidłowy token zwraca konto w user:
curl https://hivesigner.com/api/me -H 'Authorization: ACCESS_TOKEN'
Nieprawidłowy token zwraca 401 z invalid_grant. Zobacz GET /api/me.
/api/me potwierdza podpis. Jego odpowiedź nie wskazuje jednak aplikacji, dla której token powstał. Dlatego zdekoduj też token i sprawdź samodzielnie jego app, type i wiek. Token utworzony dla innej aplikacji nie może nikogo zalogować do Twojej.
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;
}
API przyjmuje tylko tokeny wskazujące aplikację. Token logowania z witryny bez konta aplikacji sprawdź samodzielnie.
Sprawdź samodzielnie
- Zdekoduj token.
- Sprawdź, czy
signed_message.typejest oczekiwanym rodzajem:postingdla tokenu dostępu,logindla tokenu logowania. - Sprawdź, czy
signed_message.appto konto Twojej aplikacji. Dla witryny bez konta aplikacji sprawdź, że go w ogóle nie ma. - Sprawdź wiek na podstawie
timestamp. - Policz skrót sha256 z
JSON.stringify({ signed_message, authors, timestamp }). - Odzyskaj klucz publiczny z
signatures[0]i tego skrótu. - Odczytaj konto
authors[0]z blockchaina Hive właśnie teraz, bo użytkownicy mogą zmieniać klucze. Odzyskany klucz musi być jednym z jego obecnych kluczy publikowania albo aktywnych. Token z/api/oauth2/tokenpodpisuje natomiast @hivesigner: dla takich przyjmij obecny klucz publikowania konta @hivesigner.
W Node.js z @ecency/sdk, który udostępnia PrivateKey, PublicKey, Signature i callRPC pod @ecency/sdk/hive:
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;
}
Użyj tego tak:
// 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 });
Biblioteka dhive (@hiveio/dhive) także się nadaje: policz skrót przez cryptoUtils.sha256(message), a klucz odzyskaj przez Signature.fromString(signatures[0]).recover(digest).toString().
Trzymaj tokeny bezpiecznie
Każdy, kto ma token publikowania, może aż do jego wygaśnięcia rozgłaszać jako ta osoba przez Twoją aplikację. Traktuj go jak hasło.
- Trzymaj tokeny na swoim serwerze albo w ciasteczku httpOnly i Secure. Tokeny odświeżania i sekret klienta trzymaj wyłącznie na serwerze.
- Nigdy nie umieszczaj tokenu w adresie, który zapisujesz w dziennikach. Przepływ z tokenem dostarcza go w ciągu zapytania Twojego adresu zwrotnego. Odczytaj go na serwerze, a potem przekieruj na adres bez niego. Ciąg zapytania adresu zwrotnego zostaw poza dziennikami.
- Na stronie adresu zwrotnego nie ładuj niczego z innych witryn, żeby adres z tokenem do nich nie trafił. Pomaga nagłówek
Referrer-Policy: no-referrerna tej stronie. - Token wysyłaj tylko na swój serwer i na
https://hivesigner.com/api/.
Wylogowanie i odebranie dostępu
- Wylogowanie osoby oznacza wyrzucenie tokenu: usuń go ze swojej sesji albo ciasteczka. Możesz też wywołać
/api/oauth2/token/revoke, aby powiedzieć Hivesigner, że osoba się wylogowała. Twoja aplikacja i tak wyrzuca token sama. - Trwałe odcięcie dostępu Twojej aplikacji to decyzja osoby. Na https://hivesigner.com/authorized-apps albo na
https://hivesigner.com/revoke/APPusuwa ona konto Twojej aplikacji ze swojego uprawnienia do publikowania w blockchainie. Po tym API nie rozgłasza już w jej imieniu przez Twoją aplikację.