Tokens

Tokens in GNAP are issued in response to a request for access from the client instance. Unlike OAuth 2, tokens are never issued in the front channel via the browser regardless of the mode or type of client.

Access token formats and values are opaque to the client, but must be known to both the AS (so that it can issue them), and sometimes to the RS (so that it can interpret them, unless it uses an externalized service for doing so). In all of these examples, we'll be using a reference-style opaque token value.

Key-bound Access Tokens

By default, tokens in GNAP are issued bound to a key. Proof of possession of the token's bound key must be presented alongside the token whenever that token is used.

Unless otherwise specified by the AS, this key is the same key and proof mechanism the client instance proved possession of in its initial request.

{
    "access_token": {
        "value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
        "access": [
            "one",
            "two",
            "and another thing"
        ]
    }
}

Bound access tokens are presented using the proofing mechanism associated with the key.

GET /resource HTTP/1.1
Host: resources.example.com
Signature-Input: gnap=("@request-target" "host" "authorization");created=1624564850;keyid="xyz-client"
Signature: gnap=:EN/rExQ/knVi61P5AFhyMGN7aVPzk/9C7nsYAWF2RvzsoV1uNxGZklM55qCIQpuhoNty4EhiH7iwuzZBbRCQcQ==:
Authorization: GNAP OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0

Bound tokens are used both at calls to the RS as well as all calls to the continuation endpoint at the AS.

AS-supplied Token Keys

Alternatively, the AS can generate a key key generated by the AS and handed to the client as part of the token response, using the same structures the client instance uses to describe its keys and bindings.

{
    "access_token": {
        "value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
        "access": [
            "one",
            "two",
            "and another thing"
        ],
        "key": {
            "proof": "httpsig",
            "jwk": {
                "kty": "EC",
                "d": "wcHNx8kkBCcBnGY39K995TShcdOFdKtaRQLGrUELqBI",
                "crv": "P-256",
                "x": "m5dnqNXawIKF3qyCfs_raR1LtTKUtyf4t2uVa4Wmd6A",
                "y": "prF8Lo5JC2JTyj2GwtaI2LWWEaRa6v6XykjUMg-9C1U",
                "alg": "ES256"
            }
        }
    }
}

The client instance then uses this specified key when presenting the token, instead of its own key. The AS could choose this mode of operation if it knows the downstream RS needs a particular kind of key presentation, or needs a derived key. However, it's more common (and therefore the default behavior) for the AS to bind to the client's own key.

Bearer Tokens

A token issued without an associated key is a bearer token, and any party with access to the token can present it to the RS. Bearer tokens are requested by setting the bearer flag in the request.

{
    "access_token": {
        "flags": [
            "bearer"
        ],
        "access": [
            "dolphin-metadata",
            "and another thing"
        ]
    }
}

If issued, a bearer token has the bearer flag of the access token section set in the response.

{
    "access_token": {
        "value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
        "access": [
            "dolphin-metadata",
            "and another thing"
        ],
        "flags": [
            "bearer"
        ]
    }
}

The client instance sends this token to the RS using Authorization header method defined in RFC6750.

GET / HTTP/1.1
Host: server.example.com
Authorization: Bearer OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0

Multiple access tokens

A client can make a request with a multi-part access_token section, which is formatted as a JSON array. Each object in the request requires a label field with names chosen by the client to differentiate the access being requested. A request would look like this.

{
    "access_token": [
        {
            "label": "token1",
            "access": [
                {
                    "type": "example.com/resource-set",
                    "actions": [
                        "read",
                        "write",
                        "dolphin"
                    ],
                    "locations": [
                        "https://server.example.net/",
                        "https://resource.local/other"
                    ],
                    "datatypes": [
                        "metadata",
                        "images"
                    ]
                }
            ]
        },
        {
            "label": "token2",
            "access": [
                {
                    "type": "example.com/another-api",
                    "actions": [
                        "foo",
                        "bar",
                        "dolphin"
                    ],
                    "locations": [
                        "https://resource.other/"
                    ],
                    "datatypes": [
                        "data",
                        "pictures"
                    ]
                }
            ]
        }
    ]
}

The returned access token structure is similar, with an array of uniquely-labeled access token objects.

{
    "access_token": [
        {
            "label": "token1",
            "value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
            "access": [
                {
                    "type": "example.com/resource-set",
                    "actions": [
                        "read",
                        "write",
                        "dolphin"
                    ],
                    "locations": [
                        "https://server.example.net/",
                        "https://resource.local/other"
                    ],
                    "datatypes": [
                        "metadata",
                        "images"
                    ]
                }
            ]
        },
        {
            "label": "token2",
            "value": "UFGLO2FDAFG7VGZZPJ3IZEMN21EVU71FHCARP4J1",
            "access": [
                {
                    "type": "example.com/another-api",
                    "actions": [
                        "foo",
                        "bar",
                        "dolphin"
                    ],
                    "locations": [
                        "https://resource.other/"
                    ],
                    "datatypes": [
                        "data",
                        "pictures"
                    ]
                }
            ]
        }
    ]
}

The token1 access token in the response corresponds directly to the token1 portion of the request, and the same applies to token2. The AS can't add an additional access token that the client did not specifically request (unless the advanced token splitting functionality is requested), but it can omit a requested access token in case it was not issued. This could be because the user denied the authorization request or some other policy decision required it. Each access token could have different flags, different management, and of course different access rights.

Managing Access Tokens

If the access token is issued alongside a token management URI:

{
    "access_token": {
        "value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
        "access": [
            "one",
            "two",
            "and another thing"
        ],
        "manage": "https://server.example.com/token/PRY5NM33OM4TB8N6BW7OZB8CDFONP219RP1L"
    }
}

Then the client can send a POST request to the token management URL to get a new access token.

The token itself is used to access the API at the management URL. If the token is bound to a specific key, the client has to present that key with the token as in any other request using the token. However, if the token is a bearer token, the client has to prove possession of the same key that was used to issue the token. That is to say, bearer tokens are never used as the sole credentials for their own management.

Rotating Access Tokens

To rotate a token, the client sends an empty HTTP POST to the token management URL.

POST /token/PRY5NM33OM4TB8N6BW7OZB8CDFONP219RP1L HTTP/1.1
Host: server.example.com
Signature-Input: gnap=("@request-target" "host" "authorization");created=1624564850;keyid="xyz-client"
Signature: gnap=:g0icu7VRfKaAmnArofz1m/S7ZdnybSO6gMncvwrUvWIXfB/DBltK0arEo5PqAv5vUKXSEb/7zkqZM4eeMf3xDg==:
Authorization: GNAP OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0

If the management URI is still valid, the AS revokes the old access token (if possible) and issues the new access token using the same format as in the response.

{
    "access_token": {
        "value": "FKPLDO5394XVIWHVNR7POUNC4OYJ2LWKYYZGKFS6",
        "access": [
            "one",
            "two",
            "and another thing"
        ],
        "manage": "https://server.example.com/token/PRY5NM33OM4TB8N6BW7OZB8CDFONP219RP1L"
    }
}

Note that since this process could result in the management URL rotating upon each use. If no management URL is returned, the client can no longer rotate the token.

If the client instance wants to get a new access token with a different set of rights, it can't use the token rotation mechanism and instead needs to send a continuation request with the new parameters.

Revoking Access Tokens

A client can request the revocation of a token by sending a DELETE request to the token management URL.

DELETE /token/PRY5NM33OM4TB8N6BW7OZB8CDFONP219RP1L HTTP/1.1
Host: server.example.com
Signature-Input: gnap=("@request-target" "host" "authorization");created=1624564850;keyid="xyz-client"
Signature: gnap=:mcHk93cUDq1h/fb073O6oQM0Ek8tT+i2yZZHQOy05yLu0iiRUmVHC0K8m/kaB1Q/SXLYZeoadClcq7TVTvmZOA==:
Authorization: Bearer OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0

Both the AS and client throw out the access token after completion of the revocation.