-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
For various applications it is desirable to be able to read or write user data. This proposal describes a format for tokens, a method for authenticating requests, and endpoints for authorizing tokens.
Token format
Each token is a JSON object with the following schema:
{
"session": String,
"expire": Int64?,
"scopes": Array(String)
"signature": String
}Tokens will be created with a given session, which allows them to be revoked by the user.
Tokens may be issued with an expire timestamp after which the token will be considered invalid.
Tokens will have a list of one or more scopes determining their permissions. The format for scopes is described below.
Tokens will have a signature to ensure integrity of the other fields.
Example token:
{
"session": "v1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"expires": 1554680038,
"scopes": [
":notifications",
":subscriptions/*",
"GET:tokens*",
],
"signature": "f//2hS20th8pALF305PJFK+D2aVtvefNnQheILHD2vU="
}Authentication
All sub-routes of /api/v1/auth must be authenticated.
Endpoints requiring authentication are authenticated with an Authentication: Bearer <token> header.
Tokens are validated by taking all key-value pairs, minus signature, and creating the following string:
key1=value1
key2=value1,value2,value3
Values are alpha-sorted. Keys are alpha-sorted and joined by newlines. There is no trailing newline.
The signature is created by signing the string using SHA256-HMAC with the instance's HMAC_KEY and Base64 encoded.
Using the above token as an example:
expires=1554680038
scopes=GET:tokens*,:notifications,:subscriptions/*
session=v1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Signed using SECRET_KEY provides the signature f//2hS20th8pALF305PJFK+D2aVtvefNnQheILHD2vU=.
Scoping
In order to provide a means of limiting permissions for an application, tokens must provide one or more scopes.
Each scope refers to a sub-route of /api/v1/auth. Scopes are defined as zero or more HTTP methods (semicolon-separated), colon (:), followed by an endpoint and optional wildcard for matching sub-routes.
A scope X is said to be containing scope Y if X can match Y, but Y cannot match X. For example, :subscriptions* contains GET:subscriptions/subscribe.
Example scope allowing access to /api/v1/auth/subscriptions using any method:
{ "scopes": [":subscriptions"] }Example scope allowing access to /api/v1/auth/subscriptions and any sub-routes using any method:
{ "scopes": [":subscriptions*"] }Example scope allowing access to any sub-routes of /api/v1/auth/subscriptions/ using only GET and POST:
{ "scopes": ["GET;POST:subscriptions/*"] }Example scope allowing access to any endpoint using any method:
{ "scopes": [":*"] }A SID cookie is equivalent to a token with "scopes": [":*"].
POST /api/v1/auth/tokens/register
The /api/v1/auth/tokens/register endpoint would support the following body (Content-Type: application/json):
{
"scopes": Array(String), // List of scopes (comma-separated)
"callbackUrl" : String?, // URL for redirecting after successful authorization, optional
"expire": Int64? // Int64, optional
}Calls to /api/v1/auth/tokens/register must already be authenticated in order to receive a new token. This can be accomplished by a signed in user or with a Bearer token with a scope containing POST:tokens/register.
Calls made using a Bearer token can only create tokens with equivalent or subsets of their own scopes.
If calls are made using a SID cookie, the user will be presented with a page presenting the requested scopes, and must then authorize the request before being redirected to callbackUrl. If no callbackUrl is provided, then after authorizing the token the user will be redirected to a page on the instance containing the newly created token.
If a callbackUrl is provided, then after successful authorization the instance will redirect to callbackUrl with ?access_token=<token> set as the query.
POST /api/v1/auth/tokens/unregister
The /api/v1/auth/tokens/unregister endpoint would support the following body:
{
"session": String?
}Tokens can be unregistered by calling /api/v1/auth/tokens/unregister. A token must have a scope containing POST:tokens/unregister in order to unregister itself. In order to unregister other tokens a token must have a scope containing GET:tokens and POST:tokens/unregister.
If the response body is empty, or session is not provided the token is assumed to be unregistering itself.
Users would also be provided a /list_tokens endpoint for viewing and degistering tokens they had authorized.
Example endpoints
All endpoints below are sub-routes of /api/v1/auth.
- GET/POST
/preferences- Would allow GET of current preferences, or POST with updated preferences - GET
/notifications- Same as/api/v1/notificationswith support for?since=, see [RFC] Push Notifications API #469 - GET
/subscriptions- Shows list of currently subscribed channels by the user- POST
/subscriptions/:ucid- Subscribe to givenucid - DELETE
/subscriptions/:ucid- Unsubscribe to givenucid
- POST
- GET
/tokens- Lists tokens authorized by user- POST
/tokens/register - POST
/tokens/unregister
- POST
Example of use
For an application with a shared pool of users, such as FreeTube or CloudTube, an example token could be:
{
"session": "v1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"scopes": [":notifications", "POST:subscriptions/*"],
"signature": "fNvXoT0MRAL9eE6lTE33CEg8HitYJDOL9a22rSN2Ihg="
}This would allow clients to use ?since=TIMESTAMP to receive any notifications missed when offline, and subscribe to new channels. DELETE:subscriptions/* is not included here to prevent unsubscribing to channels that still may need to be tracked by other clients.