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.
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:
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.)
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.