Токени
Токенът на Hivesigner е кратко подписано твърдение. То назовава Hive акаунт, приложението, за което е създадено, и времето на подписване. Вашият сървър може да провери токен през API или сам. Тази страница показва какво съдържа токенът, колко дълго е валиден и двата начина за проверка.
Как изглежда токенът
Токенът е JSON обект, кодиран в base64url, с една разлика от стандартния base64url: за допълване се използва . вместо =. Спрямо обикновения base64 + става -, / става _, а = става .. Всеки токен започва с eyJzaWduZWRfbWVzc2FnZSI6.
Декодиран, токен за достъп от потока с токен изглежда така:
{
"signed_message": { "type": "posting", "app": "myapp" },
"authors": ["alice"],
"timestamp": 1789819200,
"signatures": ["1f5a0c...e27b"],
"authority": "posting"
}
| Поле | Значение |
|---|---|
signed_message.type |
Какъв е токенът: login, posting, code или refresh. Вижте Видове токени. |
signed_message.app |
Акаунтът на приложението, за което е създаден токенът. Токен за вход от сайт без акаунт за приложение няма такъв. |
authors[0] |
Hive акаунтът, за който е токенът. |
timestamp |
Кога е подписан, в секунди от 1970-01-01 UTC. |
signatures[0] |
Подписът, като шестнадесетичен низ. |
authority |
Само в токени, подписани в браузъра: кой от ключовете на потребителя е подписал, posting или active. Това поле е извън подписаните данни. За да знаете кой ключ е подписал, възстановете го от подписа. |
Подписът е secp256k1 подпис върху sha256 хеша на JSON.stringify({ signed_message, authors, timestamp }), с ключовете в този ред.
Декодиране на токен
В Node.js:
export function decodeToken(token) {
const base64 = token.replace(/[-_.]/g, (c) => ({ '-': '+', _: '/', '.': '=' })[c]);
return JSON.parse(Buffer.from(base64, 'base64').toString('utf8'));
}
В браузър:
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));
}
Декодирането не е проверка. Всеки може да състави низ, който се декодира до този вид. Проверете токена, преди да му се доверите.
Видове токени
| Токен | type |
app |
Подписан от | Откъде го получавате |
|---|---|---|---|---|
| Токен за достъп, поток с токен | posting |
Вашето приложение | posting ключа на потребителя или неговия active ключ, когато Hivesigner няма posting ключ за акаунта | access_token на вашия адрес за обратно извикване |
Токен за вход, scope=login |
login |
Вашето приложение | posting или active ключа на потребителя | access_token на вашия адрес за обратно извикване |
| Токен за вход, сайт без акаунт за приложение | login |
Няма | posting или active ключа на потребителя | access_token на вашия адрес за обратно извикване |
| Код | code |
Вашето приложение | posting или active ключа на потребителя | code на вашия адрес за обратно извикване |
| Токен за достъп, поток с код | posting |
Вашето приложение | posting ключа на @hivesigner | /api/oauth2/token |
| Токен за обновяване | refresh |
Вашето приложение | posting ключа на @hivesigner | /api/oauth2/token |
Кодът и токенът за обновяване не са токени за достъп. Никога не приемайте нито един от тях като вход.
Колко дълго е валиден токенът
Токенът за достъп е валиден 7 дни: expires_in е 604800 секунди, броени от неговия timestamp. Когато изтече:
- Поток с токен: изпратете потребителя да влезе отново. Който вече е упълномощил приложението ви, вижда "Вход в APP" и му трябва едно кликване.
- Поток с код: вашият сървър получава нов токен за достъп с токена за обновяване и вашата клиентска тайна. Вижте Обновяване.
Смятайте токена за изтекъл веднага щом неговият timestamp стане по-стар от 7 дни. Приемайте много по-малка възраст за всичко, което проверявате веднага след пренасочването. Разменяйте кода веднага. Приемайте токен за вход само в рамките на няколко минути от неговия timestamp.
Проверете токена на своя сървър
Преди сървърът ви да се довери на токен, изпратен от браузър или приложение, проверете, че:
- акаунтът или @hivesigner наистина го е подписал;
- създаден е за вашето приложение;
- това е видът токен, който очаквате;
- достатъчно скорошен е.
Попитайте API
Извикайте /api/me с токена. Валиден токен връща акаунта в user:
curl https://hivesigner.com/api/me -H 'Authorization: ACCESS_TOKEN'
Невалиден токен връща 401 с invalid_grant. Вижте GET /api/me.
/api/me потвърждава подписа. Отговорът му не назовава приложението, за което е създаден токенът. Затова декодирайте токена и проверете сами неговите app, type и възраст. Токен, създаден за друго приложение, не бива да вписва никого във вашето.
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 приема само токени, които назовават приложение. Проверете сами токен за вход от сайт без акаунт за приложение.
Проверете го сами
- Декодирайте токена.
- Проверете, че
signed_message.typeе видът, който очаквате:postingза токен за достъп,loginза токен за вход. - Проверете, че
signed_message.appе акаунтът на вашето приложение. За сайт без акаунт за приложение проверете, че такъв няма. - Проверете възрастта от
timestamp. - Изчислете sha256 хеша на
JSON.stringify({ signed_message, authors, timestamp }). - Възстановете публичния ключ от
signatures[0]и този хеш. - Прочетете акаунта
authors[0]от блокчейна Hive сега, защото потребителите могат да сменят ключовете си. Възстановеният ключ трябва да е един от текущите му posting или active ключове. Токен от/api/oauth2/tokenе подписан от @hivesigner: за такива приемайте текущ posting ключ на акаунта @hivesigner.
В Node.js с @ecency/sdk, който изнася PrivateKey, PublicKey, Signature и callRPC под @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;
}
Използвайте го така:
// 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 });
Библиотеката dhive (@hiveio/dhive) също върши работа: изчислете хеша с cryptoUtils.sha256(message) и възстановете ключа с Signature.fromString(signatures[0]).recover(digest).toString().
Пазете токените
Всеки, който държи posting токен, може да излъчва като потребителя през вашето приложение, докато токенът не изтече. Отнасяйте се с него като с парола.
- Дръжте токените на своя сървър или в httpOnly, Secure бисквитка. Токените за обновяване и клиентската тайна дръжте само на сървъра.
- Никога не поставяйте токен в URL адрес, който записвате. Потокът с токен доставя токена в query низа на вашия адрес за обратно извикване. Прочетете го на сървъра и след това пренасочете към URL адрес без него. Оставете query низа на този адрес извън дневниците си.
- Не зареждайте нищо от други сайтове на страницата на адреса за обратно извикване, за да не се изпраща към тях адресът с токена. Заглавка
Referrer-Policy: no-referrerна тази страница помага. - Изпращайте токен само към собствения си сървър и към
https://hivesigner.com/api/.
Изход и премахване на достъпа
- Изход на потребител означава да изхвърлите токена: изтрийте го от сесията или бисквитката си. Можете също да извикате
/api/oauth2/token/revoke, за да кажете на Hivesigner, че потребителят е излязъл. Приложението ви така или иначе изхвърля токена само. - Прекратяването на достъпа на приложението ви завинаги е решение на потребителя. На https://hivesigner.com/authorized-apps или на
https://hivesigner.com/revoke/APPтой премахва акаунта на вашето приложение от своите posting правомощия в блокчейна. След това API вече не излъчва от негово име през вашето приложение.