Authorization

Authorization in Orb is typically performed using bearer tokens for client-to-server communication. For server-to-server communication, authentication is first performed using HTTP signatures and then each endpoint performs its own authorization.

Bearer Tokens

For endpoints that require authorization, a client must add a bearer token to the HTTP request header as follows:

Authorization:[Bearer mytoken]

The server matches the bearer token in the request header against the required token(s) for the particular endpoint. Each REST endpoint may be configured to require tokens for both read (GET) and write (POST) requests. If no token is defined for an endpoint then no authorization is performed. Multiple tokens may be defined for read and write requests. If more than one token is defined then authorization succeeds if any of the tokens is found in the request header. If a token for the request is required but not found in the request header then HTTP signature verification is performed.

../../_images/auth-bearer-token.svg

HTTP Signatures

A common HTTP client within Orb is used for all server-to-server communications. If HTTP signatures are enabled then the HTTP client sets additional headers on the HTTP request.

Headers

The following headers are added to the request before it is sent: Date, Digest, and Signature. For example:

Date:[Thu, 17 Feb 2022 14:29:24 GMT]
Digest:[SHA-512=vXXy/lk6D+XE8fYRTL7OS7izBUn6ntk60Rn97/gOSnbSxHZuaPvPTw1FW427qLqYpA0xGcXyPDQh4ujwret4aw==]
Signature:[keyId="https://orb.domain1.com/services/orb/keys/main-key",algorithm="Ed25519",headers="(request-target) Date Digest",signature="8AGVZi+xaDQ2kgD4sZUd4e2c2oOIkxzou2MoSSvQv72QJeSsoLa8+qJ1A+w2xkTDHNDfBTG8T/mNmtmYouv9Ag=="]

Date Header

The Date header is set to the current date/time.

Digest Header

The Digest header contains the hash of the request body, prepended by the algorithm. If a body was not included in the request then Digest will be empty.

Signature Header

The Signature header contains a comma-separated string of field-values (i.e. field1=value1,field2=value2,…) where the fields are defined as follows:

keyId

The value of the keyId field is the URI of the public key that may be used to verify the signature. The value of keyId must be resolvable via HTTP or another protocol.

algorithm

The algorithm field contains the algorithm used to sign the request. Orb uses KMS to sign the request using the Ed25519 algorithm.

headers

The headers field declares the set of headers that should be signed. The value of this field is a space-separated string that contains the following set of fields:

  1. (request target) - Includes the request method (GET, POST) and the request URI. For example: POST https://orb.domain1.com.

  2. Date - Points to the Date header.

  3. Digest - Points to the Digest header (this value is not included if the Digest header is empty).

signature

The signature field contains the base64-encoded signature of the headers that are declared in the headers field.

Signature Verification

On receiving a request, the Orb server retrieves the URI specified in the value of the keyId field from the Signature header and then sends a request to this URI. The response of the request is in the following format:

{
  "id": "https://orb.domain1.com/services/orb/keys/main-key",
  "owner": "https://orb.domain1.com/services/orb",
  "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEArK46BYVBHCM1Th+kKCFzabVmbTmTXRL5SwH+m2WvKKY=\n-----END PUBLIC KEY-----\n"
}

The value in the signature field is then verified using the public key. If not valid then an HTTP 401 Unauthorized response is sent to the client. If valid, the owner of the key is retrieved. (The owner is an ActivityPub service (actor)). Following is a sample response:

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://w3id.org/security/v1",
    "https://w3id.org/activityanchors/v1"
  ],
  "followers": "https://orb.domain1.com/services/orb/followers",
  "following": "https://orb.domain1.com/services/orb/following",
  "id": "https://orb.domain1.com/services/orb",
  "inbox": "https://orb.domain1.com/services/orb/inbox",
  "liked": "https://orb.domain1.com/services/orb/liked",
  "likes": "https://orb.domain1.com/services/orb/likes",
  "outbox": "https://orb.domain1.com/services/orb/outbox",
  "publicKey": {
    "id": "https://orb.domain1.com/services/orb/keys/main-key",
    "owner": "https://orb.domain1.com/services/orb",
    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEArK46BYVBHCM1Th+kKCFzabVmbTmTXRL5SwH+m2WvKKY=\n-----END PUBLIC KEY-----\n"
  },
  "shares": "https://orb.domain1.com/services/orb/shares",
  "type": "Service",
  "witnesses": "https://orb.domain1.com/services/orb/witnesses",
  "witnessing": "https://orb.domain1.com/services/orb/witnessing"
}

The value of the publicKey.id field in the ActivityPub service (actor) is then validated against the ID of the public key to ensure that they match. If they don’t match then an HTTP 401 Unauthorized response is sent to the client. If they do match then authentication has succeeded and the request, along with the actor, is forwarded to the appropriate handler. (The actor may be used by the handler to perform authorization.)

../../_images/httpsignatures.svg

Note that public keys and actors are cached (with an expiry) so that remote calls aren’t required each time a signature verification is performed.