RFC 9767
Grant Negotiation and Authorization Protocol Resource Server Connections
Internet Engineering Task Force (IETF) ๐ Request for Comments: 9767
Okay bestie, here's the title block tea โโจ
This doc is RFC 9767, titled Grant Negotiation and Authorization Protocol Resource Server Connections. It's a Standards Track document, which means it's the real deal, the Internet Standards Track, and it went through public review and got approved by the IESG ๐ . It was published in April 2025 and it's from the IETF, with the ISSN 2070-1721 ๐ฆ.
The editor is J. Richer, Ed. from Bespoke Engineering, and co-author F. Imbault from acert.io ๐ซถ. Nothing in the title block says it obsoletes or updates any other RFC, so it's a standalone extension ๐ธ.
Abstract โจ
okay so GNAP (the Grant Negotiation and Authorization Protocol) is the protocol that lets software get permission to act on someone's behalf ๐. It defines how the client gets delegated authorization and how the results get passed back to it ๐. This extension is the bit that teaches resource servers (RSs) how to talk to authorization servers (ASs) in a way that actually works across different systems, which is literally the whole point ๐.
Status of This Memo โจ
This is an Internet Standards Track document ๐.
It's a product of the IETF and it represents the consensus of the IETF community ๐. It got public review and the IESG approved it for publication ๐ฅ. If you want to know more about Internet Standards, check out Section 2 of RFC 7841 ๐.
For the current status, errata, and how to send feedback, head to https://www.rfc-editor.org/info/rfc9767 ๐ง.
Copyright Notice โจ
Copyright (c) 2025 IETF Trust and the persons identified as the document authors. All rights reserved ๐ .
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication ๐. Please read these carefully because they spell out your rights and restrictions ๐. Code Components pulled out of this document must include Revised BSD License text, as described in Section 4.e of the Trust Legal Provisions, and they come without warranty as described in that license ๐ซถ.
Table of Contents ๐ธ
1. Introduction
1.1. Terminology
2. Access Tokens
2.1. General-Purpose Access Token Model
2.1.1. Value
2.1.2. Issuer
2.1.3. Audience
2.1.4. Key Binding
2.1.5. Flags
2.1.6. Access Rights
2.1.7. Time Validity Window
2.1.8. Token Identifier
2.1.9. Authorizing Resource Owner
2.1.10. End User
2.1.11. Client Instance
2.1.12. Label
2.1.13. Parent Grant Request
2.1.14. AS-Specific Access Tokens
2.2. Access Token Formats
3. Resource-Server-Facing API
3.1. RS-Facing AS Discovery
3.2. Protecting RS Requests to the AS
3.3. Token Introspection
3.4. Registering a Resource Set
3.5. Error Responses
4. Deriving a Downstream Token
5. IANA Considerations
5.1. Well-Known URIs
5.2. GNAP Grant Request Parameters
5.3. GNAP Token Formats
5.3.1. Registry Template
5.3.2. Initial Registry Contents
5.4. GNAP Token Introspection Request
5.4.1. Registry Template
5.4.2. Initial Registry Contents
5.5. GNAP Token Introspection Response
5.5.1. Registry Template
5.5.2. Initial Registry Contents
5.6. GNAP Resource Set Registration Request Parameters
5.6.1. Registry Template
5.6.2. Initial Registry Contents
5.7. GNAP Resource Set Registration Response Parameters
5.7.1. Registry Template
5.7.2. Initial Registry Contents
5.8. GNAP RS-Facing Discovery Document Fields
5.8.1. Registry Template
5.8.2. Initial Registry Contents
5.9. GNAP RS-Facing Error Codes
5.9.1. Registration Template
5.9.2. Initial Contents
6. Security Considerations
6.1. TLS Protection in Transit
6.2. Token Validation
6.3. Caching Token Validation Result
6.4. Key Proof Validation
6.5. Token Exfiltration
6.6. Token Reuse by an RS
6.7. Token Format Considerations
6.8. Oversharing Token Contents
6.9. Resource References
6.10. Token Reissuance from an Untrusted AS
6.11. Introspection of Token Keys
6.12. RS Registration and Management
7. Privacy Considerations
7.1. Token Contents
7.2. Token Use Disclosure through Introspection
7.3. Mapping a User to an AS
8. References
8.1. Normative References
8.2. Informative References
Acknowledgements
Authors' Addresses
1. Introduction ๐ง
okay so the core GNAP spec [GNAP] defines separate roles for the authorization server (AS) and the resource server (RS) ๐ . But it doesn't say how the RS gets answers to the big questions, like whether an access token is still valid ๐ or what access rights it's approved for โจ.
It's totally possible for the AS and RS to be tightly coupled, like one server with shared storage ๐ . But GNAP doesn't assume or require that, and it's more and more common for them to be run and managed separately, especially when one AS protects a bunch of RSs at once ๐.
This spec defines a set of RS-facing APIs that an AS can offer for those loosely coupled setups ๐ฆ. It also defines a general-purpose model for access tokens, which can show up in structured, formatted tokens or in token introspection responses ๐. And there's a method for an RS to derive a downstream token for calling another chained RS ๐.
How the AS issues the access token to the client instance, and how the client instance presents it to the resource server, are handled by the core GNAP spec [GNAP] ๐.
1.1. Terminology ๐
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. ๐ฅ
This doc has non-normative examples of partial and complete HTTP messages, JSON structures, URLs, query components, keys, and other stuff ๐. Some examples use a single trailing backslash \ to show line wrapping for long values, as per [RFC8792] โจ. That \ character and the leading spaces on wrapped lines aren't part of the value ๐ซถ.
Terms specific to GNAP are defined in the core spec's terminology section, so check Section 1.1 of [GNAP] ๐. The protocol roles are: authorization server, client, end user, resource owner, and resource server ๐. The protocol elements are: access token, attribute, grant, privilege, protected resource, right, subject, and subject information ๐ธ. This document uses the same definitions ๐ .
2. Access Tokens ๐ฆ
okay so access tokens are how an AS gives a client instance limited access to an RS ๐. They're artifacts that represent a particular set of access rights the client instance has been given to act on behalf of the RO ๐. The format varies between systems (see Section 2.2), but the concept of an access token is the same across every GNAP system โจ.
2.1. General-Purpose Access Token Model ๐
The core GNAP spec [GNAP] is all about the relationship between the client and the AS ๐. Since the access token is opaque to the client, the core spec doesn't define a token model ๐. But the AS has to create tokens and the RS has to understand them, so this doc presents a common access token model to get some structural interoperability going ๐ง. These aspects are shared across different GNAP deployments ๐. The list isn't meant to be universal or complete. It's more of a guide for implementers building data structures and systems around a GNAP deployment ๐. These data structures get passed between the AS and RS either as a structured token or through an API-like mechanism such as token introspection (see Section 3.3) ๐.
This model doesn't assume either approach, and both can be used together to pass different pieces of info ๐ . Where it's possible, the model maps to the JSON Web Token (JWT) [JWT] standard format for each item โจ.
2.1.1. Value ๐
Every access token has a _value_, which is the string passed on the wire between parties ๐ฆ. To tell different access tokens apart at runtime, the value has to be unique within a security domain (like all systems an AS controls) ๐. Otherwise two separate tokens could get mixed up, and that would cause security issues ๐ญ. The AS picks the value, which can be structured (see Section 2.2) or unstructured ๐. When the token is structured, the value also has a _format_ that the AS and RS both know, and the other items in this model are packed inside the value somehow ๐. When it's unstructured, the RS usually looks up the values with a service like token introspection, described in Section 3.3 ๐.
The access token value gets sent in the value field of an access_token response; see Section 3.2 of [GNAP] ๐ธ.
The client software is never expected or intended to understand the token value itself, since the format and content are opaque to it ๐. It needs to be able to carry and present the value, but that's all ๐ซถ.
If structured tokens like the ones in [JWT] are used, the AS might not store the value of the token ๐ง. Instead, a token identifier can be used along with an AS-generated signature to validate and identify an individual token ๐.
2.1.2. Issuer ๐๏ธ
The AS issues the access token as defined in [GNAP] ๐. The AS needs to identify itself so an RS can recognize the tokens it issued, especially when tokens from multiple ASs could show up at the same RS ๐.
This info usually isn't passed directly to the client instance, since the client instance should already know it from where it got the token ๐ .
In the payload of a JSON Web Token [JWT] or a token introspection response, this maps to the iss claim ๐.
2.1.3. Audience ๐ฏ
The access token is meant for one or more RSs ๐. The AS can list the intended RSs so each RS can make sure it isn't getting a token meant for someone else ๐ก๏ธ. The AS and RS have to agree on what the audience identifiers look like, but RS URIs are a common pattern ๐ฆ.
In the payload of a JSON Web Token [JWT] or token introspection response, this maps to the aud claim โจ.
When more complex access is needed, the location field of objects in the access array can also carry audience info ๐. In those cases the client instance might need to know the audience info so it can tell apart the possible RSs it could present the token to ๐ง.
2.1.4. Key Binding ๐
Okay so access tokens in GNAP are bound to the client instance's registered or presented key ๐, except when the access token is a bearer token (no key, no binding, lol ๐ ). For every token bound to a key, the AS and RS need to be able to tell which key it's bound to, bestie, because otherwise an attacker could swap in their own key while presenting the token ๐ฑ For an asymmetric algorithm, the AS and RS only need the public key, but the client instance also needs the private key so it can present the token. For a symmetric algorithm, all parties need to either know the shared key or be able to derive it. ๐ซถ
Where the key info comes from can vary depending on deployment choices ๐ฆ For example, an AS could decide that every token issued to a client instance is always bound to that instance's current key. So when the key needs to be dereferenced, the AS looks up the client instance the token was issued to and finds the key info there โจ Or the AS could bind each token to a specific key that's managed separately from client instance info. In that case, the AS figures out the key info directly. This lets the client instance use a different key for each request, or lets the AS issue a key for the client instance to use with one particular token. ๐
In every case, the key binding also includes a proofing mechanism, along with any parameters that mechanism needs, like a signing or digest algorithm. ๐ฅ If that info isn't included with the proofing key, an attacker could present a token with a seemingly valid key and use an insecure, incorrect proofing mechanism. Yikes ๐
This value is conveyed to the client instance in the key field of the access_token response in Section 3.2 of [GNAP]. Since the common case is that the token is bound to the client instance's registered key, this field can be omitted in that case, because the client already knows its own key ๐
In the payload of a JSON Web Token [JWT], this corresponds to the cnf (confirmation) claim. In a token introspection response, this corresponds to the key claim. ๐
In the case of a bearer token, all parties need to know that a token has no key bound to it, so they'll reject any attempt to use the bearer token with a key in an undefined way ๐ซ
2.1.5. Flags ๐ฉ
GNAP access tokens can have multiple associated data flags that indicate special processing or considerations for a token. For example, the data flags can indicate whether a token is a bearer token or should be expected to be durable across grant updates. ๐
The client can request a set of flags using the flags field of the access_token grant request parameter in Section 2.1.1 of [GNAP] ๐ง
These flags are conveyed from the AS to the client in the flags field of the access_token section of the grant response in Section 3.2 of [GNAP].
For token introspection, flags are returned in the flags field of the response.
2.1.6. Access Rights ๐ก๏ธ
Access tokens are tied to a limited set of access rights. These rights specify in some detail what the token can be used for, how it can be used, and where it can be used. The internal structure of access rights is detailed in Section 8 of [GNAP]. It's giving detailed ๐
The access rights associated with an access token are calculated from the rights available to the client instance making the request, the rights available to be approved by the RO, the rights actually approved by the RO, and the rights corresponding to the RS in question. The rights for a specific access token are a subset of the overall rights in a grant request. Main character energy, but make it bounded ๐
These rights are requested by the client instance in the access field of the access_token request; see Section 2.1 of [GNAP].
The rights associated with an issued access token are conveyed to the client instance in the access field of the access_token response in Section 3.2 of [GNAP].
In token introspection responses, access rights correspond to the access claim.
2.1.7. Time Validity Window โฐ
The access token can be limited to a certain time window, outside of which it's no longer valid for use at an RS. This window can be explicitly bounded by an expiration time and a not-before time, or it could be calculated based on the issuance time of the token. For example, an RS could decide that it will accept tokens for most calls within an hour of a token's issuance, but only within five minutes of the token's issuance for certain high-value calls. Iconic restraint ๐ฅน
Since access tokens could be revoked at any time, for any reason, outside of a client instance's control, the client instance often doesn't know or concern itself with the validity time window of an access token. However, this information can be made available to it by using the expires_in field of an access token response; see Section 3.2 of [GNAP] ๐
The issuance time of the token is conveyed in the iat claim in the payload of a JSON Web Token [JWT] or a token introspection response.
The expiration time of a token, after which it is to be rejected, is conveyed in the exp claim in the payload of a JSON Web Token [JWT] or a token introspection response.
The starting time of a token's validity window, before which it is to be rejected, is conveyed in the nbf claim in the payload of a JSON Web Token [JWT] or a token introspection response.
2.1.8. Token Identifier ๐ชช
Individual access tokens often need a unique internal identifier so the AS can tell multiple separate tokens apart. This value of the token can often be used as the identifier, but in some cases, a separate identifier is used. ๐ฆ
This separate identifier can be conveyed in the jti claim in the payload of a JSON Web Token [JWT] or a token introspection response.
This identifier is not usually exposed to the client instance using the token, because the client instance only needs to use the token by value. Need-to-know energy ๐คซ
2.1.9. Authorizing Resource Owner ๐
Access tokens are approved on behalf of a resource owner (RO). The identity of this RO can be used by the RS to determine exactly which resource to access or which kinds of access to allow. For example, an access token used to access identity information can hold a user identifier so the RS knows which profile info to return. The nature of this information is subject to agreement by the AS and RS. ๐ธ
This corresponds to the sub claim in the payload of a JSON Web Token [JWT] or a token introspection response.
Detailed RO information is not returned to the client instance when an access token is requested alone. In many cases, returning this information to the client instance would be a privacy violation on the part of the AS ๐ โโ๏ธ Since the access token represents a specific delegated access, the client instance only needs to use the token at its target RS. Following the profile example, the client instance doesn't need to know the account identifier to get specific attributes about the account represented by the token.
GNAP does allow for the return of subject information separately from the access token, in the form of identifiers and assertions. These values are returned directly to the client, separately from any access tokens that are requested, though it's common that they represent the same party. โจ
2.1.10. End User ๐งโ๐ป
The end user is the party operating the client software. The client instance can facilitate the end user interacting with the AS in order to determine the end user's identity, gather authorization, and provide the results of that information back to the client instance. Lowkey the main event ๐ฌ
In many instances, the end user is the same party as the resource owner. However, in some cases, the two roles can be fulfilled by different people, where the RO is consulted asynchronously. The token model should be able to reflect these kinds of situations by representing the end user and RO separately. For example, an end user can request a financial payment, but the RO is the holder of the account that the payment would be withdrawn from. The RO would be consulted for approval by the AS outside of the flow of the GNAP request. A token in such circumstances would need to show both the RO and end user as separate entities. Two people, two separate entries, no mixing ๐
2.1.11. Client Instance ๐ฑ
Access tokens are issued to a specific client instance by the AS. The identity of this instance can be used by the RS to allow specific kinds of access or other attributes about the access token. For example, an AS that binds all access tokens issued to a particular client instance to that client instance's most recent key rotation would need to be able to look up the client instance in order to find the key binding detail. ๐
This corresponds to the client_id claim in the payload of a JSON Web Token [JWT] or the instance_id field of a token introspection response.
The client is not normally informed of this information separately, since a client instance can usually correctly assume that it is the client instance to which a token it receives was issued. It already knows who it is, bestie ๐
2.1.12. Label ๐ท๏ธ
When multiple access tokens are requested or a client instance uses token labels, the parties will need to keep track of which labels were applied to each individual token. Since labels can be reused across different grant requests, the token label alone is not sufficient to uniquely identify a given access token in a system. However, within the context of a grant request, these labels are required to be unique. Labels, but make it a grant-request thing ๐
A client instance can request a specific label using the label field of an access_token request; see Section 2.1 of [GNAP].
The AS can inform the client instance of a token's label using the label field of an access_token response; see Section 3.2 of [GNAP].
This corresponds to the label field of a token introspection response.
2.1.13. Parent Grant Request ๐
okay so every access token is born inside a specific grant request from a client instance ๐ซถ and the grant request is basically a unique tuple (a one-of-a-kind combo, iykyk) made of:
- The AS processing the grant request
- The client instance making the grant request
- The RO (or set of ROs) approving the grant request (or needing to approve it)
- The access rights granted by the RO
- The current state of the grant request, as defined in Section 1.5 of [GNAP]
the AS can use all of this to tie shared info to specific tokens ๐ง โจ instead of writing the client instance on every single access token for a grant, the AS can stash the client info in the grant itself and just look it up by reference from the token. so efficient, I'm obsessed ๐ญ
the AS can also use this when a grant request gets updated. like, if the client instance asks for a new access token from an existing grant, the AS can use that link to revoke the older non-durable access tokens that were already issued under that grant ๐๏ธ main character energy for sure ๐
a client instance will have its own model of an ongoing grant request ๐ฆ especially if that grant request can be continued using the API defined in Section 5 of [GNAP], because then there are a bunch of pieces of statefulness to juggle. the client instance might need to keep an association with the grant request that issued the token, just in case the access token expires or doesn't have enough access rights. that way it can get a new access token without having to restart the whole grant request process from scratch ๐ธ
last bit: since the grant itself doesn't need to be identified in any of the protocol messages, GNAP does not define a specific grant identifier to be passed between any parties in the protocol ๐ only the AS needs to keep an explicit connection between an issued access token and the parent grant that issued it. that's it, that's the tea ๐
2.1.14. AS-Specific Access Tokens ๐
okay this one is serious bestie ๐ค when an access token is used for the grant continuation API defined in Section 5 of [GNAP] (the continuation access token), or the token management API defined in Section 6 of [GNAP] (the token management access token), or the RS-facing API defined in Section 3 (the resource server management access token), the AS MUST separate these access tokens from other access tokens used at one or more RSs. the AS can do this with a flag on the access token data structure, a special internal access right, or any other means it has at its disposal ๐ฅ just like other GNAP access tokens, the contents of these AS-specific access tokens are opaque to the software presenting the token. but here's the twist ๐ unlike other access tokens, their contents are also opaque to the RS.
the client instance only gets continuation access tokens as part of the continue field of the grant response in Section 3.1 of [GNAP] ๐ and it only gets token management access tokens as part of the manage field of the grant response in Section 3.2.1 of [GNAP]. how the RS gets resource server management access tokens is out of scope for this spec ๐ง but ideas include preconfiguring the token value into the RS software, or granting the access token through a standard GNAP process.
now for continuation access tokens and token management access tokens, a client instance MUST take steps to tell these special-purpose tokens apart from access tokens used at one or more RSs. to help with that, a client instance can store AS-specific access tokens separately from other access tokens, so nobody gets confused and uses them at the wrong endpoints ๐ โโ๏ธ
an RS should never see an AS-specific access token presented, so any attempt to process one MUST fail. when introspection is used, the AS MUST NOT return an active value of true for AS-specific access tokens to the RS. and if an AS implements its protected endpoints so that it uses token introspection internally, the AS MUST differentiate these AS-specific access tokens from those issued for use at an external RS. ๐จ
2.2. Access Token Formats ๐
when the AS issues an access token for use at an RS, the RS needs some way of understanding what the token is for so it knows how to respond to the request. the core GNAP protocol doesn't make any assumptions or demands about the format or contents of the access token. in fact, the token format and contents are opaque to the client instance ๐ซฃ however, token formats can totally be the subject of agreements between the AS and RS ๐ค
self-contained structured token formats let information travel between the AS and RS without the RS having to call the AS at runtime, as described in Section 3.3 โจ structured tokens can also be combined with introspection, so the token itself carries one kind of info and the introspection response carries another. two for one, slay ๐
some token formats, like Macaroons [MACAROON] and Biscuits [BISCUIT], let the RS derive sub-tokens without having to call the AS, as described in Section 4 ๐ง
the supported token formats can be communicated dynamically at runtime between the AS and RS in a few places:
- The AS can declare its supported token formats as part of RS-facing discovery (Section 3.1).
- The RS can require a specific token format be used to access a registered resource set (Section 3.4).
- The AS can return the token's format in an introspection response (Section 3.3).
anywhere the token format is listed explicitly, it MUST be one of the registered values in the "GNAP Token Formats" registry, Section 5.3 ๐ no freestyling allowed, periodt.
3. Resource-Server-Facing API ๐
to make runtime and dynamic connections with an RS easy, the AS can offer an RS-facing API made of one or more of these optional pieces:
- Discovery
- Introspection
- Token chaining
- Resource reference registration
3.1. RS-Facing AS Discovery ๐
a GNAP AS that offers RS-facing services can publish its features on a well-known discovery document ๐ฃ it lives at the URL with the same scheme and authority as the grant request endpoint URL, at the path /.well-known/gnap-as-rs.
the discovery response is a JSON document [RFC8259] made of a single JSON object with these fields:
grant_request_endpoint(string): The location of the AS's grant request endpoint defined by Section 9 of [GNAP]. This URL MUST be the same URL used by client instances for GNAP requests. The RS can use this to derive downstream access tokens, if the AS supports it. The location MUST be a URL [RFC3986] with a scheme component that MUST be https, a host component, and (optionally) port, path, and query components and no fragment components. REQUIRED. See Section 4. ๐introspection_endpoint(string): The URL of the endpoint offering introspection. The location MUST be a URL [RFC3986] with a scheme component that MUST be https, a host component, and (optionally) port, path, and query components and no fragment components. REQUIRED if the AS supports introspection. An absent value means the AS does not support introspection. See Section 3.3. ๐ต๏ธโโ๏ธtoken_formats_supported(array of strings): A list of token formats supported by this AS. The values in this list MUST be registered in the "GNAP Token Formats" registry per Section 5.3. OPTIONAL. ๐งพresource_registration_endpoint(string): The URL of the endpoint offering resource registration. The location MUST be a URL [RFC3986] with a scheme component that MUST be https, a host component, and (optionally) port, path, and query components and no fragment components. REQUIRED if the AS supports dynamic resource registration. An absent value means the AS does not support this feature. See Section 3.4. ๐key_proofs_supported(array of strings): A list of the AS's supported key proofing mechanisms. The values of this list correspond to possible values of theprooffield of the key section of the request. Values MUST be registered in the "GNAP Key Proofing Methods" registry established by [GNAP]. OPTIONAL. ๐
additional fields are defined in the "GNAP RS-Facing Discovery Document Fields" registry, see Section 5.8 ๐
3.2. Protecting RS Requests to the AS ๐ก๏ธ
unless otherwise specified, the RS MUST protect its calls to the AS using any of the signature methods defined in Section 7 of [GNAP] ๐
the RS MAY present its keys by reference or by value, kind of like a client instance calling the AS in the core GNAP protocol, as described in Section 7.1 of [GNAP] ๐ซ in the protocols defined here, the resource server identifies itself by using a key field or by passing an instance identifier directly.
by value, it looks like this:
POST /continue HTTP/1.1
Host: server.example.com
Authorization: GNAP 80UPRY5NM33OMUKMKSKU
Signature-Input: sig1=...
Signature: sig1=...
Content-Type: application/json
"resource_server": {
"key": {
"proof": "httpsig",
"jwk": {
"kty": "EC",
"crv": "secp256k1",
"kid": "2021-07-06T20:22:03Z",
"x": "-J9OJIZj4nmopZbQN7T8xv3sbeS5-f_vBNSy_EHnBZc",
"y": "sjrS51pLtu3P4LUTVvyAIxRfDV_be2RYpI5_f-Yjivw"
}
}
}
and by reference:
POST /continue HTTP/1.1
Host: server.example.com
Signature-Input: sig1=...
Signature: sig1=...
Content-Type: application/json
{
"resource_server": "7C7C4AZ9KHRS6X63AJAO"
}
how an RS's keys become known to the AS is out of scope for this spec ๐ the AS MAY require an RS to preregister its keys, or it could allow calls from arbitrary keys in a trust-on-first-use model ๐คทโโ๏ธ
the AS MAY issue access tokens, called "resource server management access tokens", to the RS to protect the RS-facing API endpoints. if those tokens are issued, the RS MUST present them to the RS-facing API endpoints along with the RS authentication ๐๏ธ
POST /continue HTTP/1.1
Host: server.example.com
Authorization: GNAP 80UPRY5NM33OMUKMKSKU
Signature-Input: sig1=...
Signature: sig1=...
Content-Type: application/json
{
"resource_server": "7C7C4AZ9KHRS6X63AJAO"
}
3.3. Token Introspection ๐
okay bestie, so the AS issues access tokens that represent a bundle of delegated access rights, and those tokens get used at one or more RSs ๐. the AS can offer an introspection service so an RS can double-check that a given access token:
- has been issued by the AS
- is valid at the current time
- has not been revoked
- is appropriate for the RS identified in the call
when the RS gets an access token, it can call the introspection endpoint at the AS to grab the token info โจ
+--------+ +------+ +------+
| Client +--(1)->| RS | | AS |
|Instance| | +--(2)->| |
| | | | | |
| | | |<-(3)--+ |
| | | | +------+
| |<-(4)--+ |
+--------+ +------+
here's the flow ๐ :
- the client instance calls the RS with its access token.
- the RS introspects the access token value at the AS. the RS signs the request with its own key (not the client instance's key or the token's key).
- the AS validates the access token value and the RS's request, then returns the introspection response for the token.
- the RS fulfills the request from the client instance.
the RS signs the request with its own key and sends the access token value in the body as a JSON object with these members ๐ง:
access_token(string): the access token value presented to the RS by the client instance. REQUIRED.proof(string): the proofing method the client instance used to bind the token to the RS request. The value MUST be registered in the "GNAP Key Proofing Methods" registry. RECOMMENDED.resource_server(object/string): the identification used to authenticate the resource server making this call, either by value or by reference as described in Section 3.2. REQUIRED.access(array of strings/objects): the minimum access rights required to fulfill the request. This MUST be in the format described in Section 8 of [GNAP]. OPTIONAL.
more fields live in the "GNAP Token Introspection Request" registry (Section 5.4) ๐๏ธ
POST /introspect HTTP/1.1
Host: server.example.com
Content-Type: application/json
Signature-Input: sig1=...
Signature: sig1=...
Digest: sha256=...
{
"access_token": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
"proof": "httpsig",
"resource_server": "7C7C4AZ9KHRS6X63AJAO"
}
the AS MUST validate the access token value and determine if the token is active ๐ต๏ธโโ๏ธ the request parameters give the AS context for evaluating the token, and the AS MUST take all provided parameters into account when deciding if the token is active. if the AS can't process part of the request (like not understanding part of the access field presented), the AS MUST NOT indicate the token as active ๐ซ
an active access token is one that is ALL of the following:
- was issued by the processing AS,
- has not been revoked,
- has not expired,
- is bound using the proof method indicated,
- is appropriate for presentation at the identified RS, and
- is appropriate for the access indicated (if present).
the AS responds with a data structure describing the token's current state, plus any info the RS would need to validate the token's presentation, like its intended proofing mechanism and key material ๐
active(boolean): If true, the access token presented is active, as defined above. If any of the criteria for an active token are not true, or if the AS is unable to make a determination (such as the token is not found), the value is set to false and other fields are omitted. REQUIRED.
if the access token is active, additional fields from the single access token response structure defined in Section 3.2.1 of [GNAP] are included. the main ones are:
access(array of strings/objects): The access rights associated with this access token. This MUST be in the format described in Section 8 of [GNAP]. This array MAY be filtered or otherwise limited for consumption by the identified RS, including being an empty array, which indicates that the token has no explicit access rights that can be disclosed to the RS. REQUIRED.key(object/string): if the token is bound. The key bound to the access token, to allow the RS to validate the signature of the request from the client instance. If the access token is a bearer token, this MUST NOT be included. REQUIREDflags(array of strings): The set of flags associated with the access token. OPTIONAL.exp(integer): The timestamp after which this token is no longer valid. Expressed as integer seconds from UNIX Epoch. OPTIONAL.iat(integer): The timestamp at which this token was issued by the AS. Expressed as integer seconds from UNIX Epoch. OPTIONAL.nbf(integer): The timestamp before which this token is not valid. Expressed as integer seconds from UNIX Epoch. OPTIONAL.aud(string or array of strings): Identifiers for the resource servers this token can be accepted at. OPTIONAL.sub(string): Identifier of the resource owner who authorized this token. OPTIONAL.iss(string): Grant endpoint URL of the AS that issued this token. REQUIRED.instance_id(string): The instance identifier of the client instance that the token was issued to. OPTIONAL.
more fields are defined in the "GNAP Token Introspection Response" registry (Section 5.5) ๐
the response MAY include any additional fields defined in an access token response, and it MUST NOT include the access token value itself ๐ โโ๏ธ
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"active": true,
"access": [
"dolphin-metadata", "some other thing"
],
"key": {
"proof": "httpsig",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "xyz-1",
"alg": "RS256",
"n": "kOB5rR4Jv0GMeL...."
}
}
}
when the RS processes the introspection results, the RS MUST determine the appropriate course of action ๐ง for instance, if the RS decides the token's access rights aren't enough for the request it came with, the RS can return an error or a public resource, whatever fits the RS. in all cases, the final determination of the response is at the discretion of the RS ๐
3.4. Registering a Resource Set ๐ฎ
if the RS needs to, it can post a set of resources (see Section 8 "Resource Access Rights" of [GNAP]) to the AS's resource registration endpoint, along with info about what the RS will need to validate the request ๐
access(array of objects/strings): The list of access rights associated with the request in the format described in Section 8 ("Resource Access Rights") of [GNAP]. REQUIRED.resource_server(object/string): The identification used to authenticate the resource server making this call, either by value or by reference as described in Section 3.2. REQUIRED.token_formats_supported(array of strings): The list of token formats that the RS is able to process. The values in this array MUST be registered in the "GNAP Token Formats" registry per Section 5.3. If the field is omitted, the token format is at the discretion of the AS. If the AS does not support any of the requested token formats, the AS MUST return an error to the RS. OPTIONAL.token_introspection_required(boolean): If present and set to true, the RS expects to make a token introspection request as described in Section 3.3. If absent or set to false, the RS does not anticipate needing to make an introspection request for tokens relating to this resource set. If the AS does not support token introspection for this RS, the AS MUST return an error to the RS. OPTIONAL.
more fields are defined in the "GNAP Resource Set Registration Request Parameters" registry (Section 5.6) ๐๏ธ
the RS MUST identify itself with its own key and sign the request ๐
POST /resource HTTP/1.1
Host: server.example.com
Content-Type: application/json
Signature-Input: sig1=...
Signature: sig1=...
Digest: ...
{
"access": [
{
"actions": [
"read",
"write",
"dolphin"
],
"locations": [
"https://server.example.net/",
"https://resource.local/other"
],
"datatypes": [
"metadata",
"images"
]
},
"dolphin-metadata"
],
"resource_server": "7C7C4AZ9KHRS6X63AJAO"
}
the AS responds with a reference that represents the resources list the RS presented, plus any extra info the RS might need in future requests ๐ง
resource_reference(string): A single string representing the list of resources registered in the request. The RS MAY make this handle available to a client instance as part of a discovery response as described in Section 9.1 of [GNAP] or as documentation to client software developers. REQUIRED.instance_id(string): An instance identifier that the RS can use to refer to itself in future calls to the AS, in lieu of sending its key by value. See Section 3.2. OPTIONAL.introspection_endpoint(string): The introspection endpoint of this AS that is used to allow the RS to perform token introspection. See Section 3.3. OPTIONAL.
more fields are defined in the "GNAP Resource Set Registration Response Parameters" registry (Section 5.7) ๐
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"resource_reference": "FWWIKYBQ6U56NL1"
}
if a resource was previously registered, the AS MAY return the same resource reference value as in previous responses ๐
if the registration fails, the AS returns HTTP status code 400 (Bad Request) to the RS, indicating that the registration was not successful ๐
the client instance can then use the resource_reference value as a string-type access reference as defined in Section 8.1 of [GNAP] ๐ฆ this value MAY be combined with any other additional access rights requested by the client instance โจ
{
"access_token": {
"access": [
"FWWIKYBQ6U56NL1",
{
"type": "photo-api",
"actions": [
"read",
"write",
"dolphin"
],
"locations": [
"https://server.example.net/",
"https://resource.local/other"
],
"datatypes": [
"metadata",
"images"
]
},
"dolphin-metadata"
]
},
"client": "client-12351.bdxqf"
}
3.5. Error Responses โจ
okay bestie, so when the RS-facing API hits an error, the AS sends back an HTTP status code 400 (Bad Request) ๐
โโ๏ธ with a JSON object that has one error field. That field can be either an object or a string, and honestly both are giving ๐
If it's a string, the string is the error code, like this:
{
error: "invalid_access"
}
If it's an object, the object holds these fields:
code (string): a single ASCII error code that defines the error. REQUIRED. ๐ฅ
description (string): a human-readable description of the error, meant for the developer of the client. OPTIONAL. ๐
{
"error": {
"code": "invalid_access",
"description": "Access to 'foo' is not permitted for this RS."
}
}
This spec defines these error code values:
"invalid_request": the request is missing a required parameter, includes an invalid parameter value, or is otherwise malformed. Messy ๐
"invalid_resource_server": the request came from an RS the AS doesn't recognize or allow, or the RS's signature validation failed. Not it ๐
"invalid_access" the RS is not permitted to register or introspect for the requested "access" value. Big no ๐ซ
More error codes can be defined in the "GNAP RS-Facing Error Codes" registry (Section 5.9). โจ
4. Deriving a Downstream Token ๐ฆ
Some architectures need an RS to act as a client instance and use a derived access token for a secondary RS. The RS isn't the same entity that made the original grant request, so it can't reference or modify that existing grant. So the RS needs to request or generate a new access token for its own use at the secondary RS. This internal secondary token is issued in the context of the incoming access token. It's giving nested ๐ช
You could use a token format (Section 2) that lets the RS make its own secondary token. But the AS can also let the RS request that secondary token using the same process the original client instance used to get the primary token. Since the RS is acting as its own client instance from GNAP's point of view, this uses the same grant endpoint, request structure, and response structure as a regular client instance request. Efficient and iconic โจ
+--------+ +-------+ +------+ +-------+
| Client +--(1)->| RS1 | | AS | | RS2 |
|Instance| | +--(2)->| | | |
| | | |<-(3)--+ | | |
| | | | +------+ | |
| | | | | |
| | | +-----------(4)------->| |
| | | |<----------(5)--------+ |
| |<-(6)--+ | | |
+--------+ +-------+ +-------+
- The client instance calls RS1 with an access token. ๐
- RS1 presents that token to the AS to get a derived token for use at RS2. RS1 says it has no ability to interact with the RO. Note that RS1 signs its request with its own key, not the token's key or the client instance's key. ๐
- The AS returns a derived token to RS1 for use at RS2. ๐
- RS1 calls RS2 with the token from (3). ๐ฒ
- RS2 fulfills the call from RS1. ๐
- RS1 fulfills the call from the original client instance. ๐
If the RS needs to derive a token from one presented to it, it can ask the AS for one by making a token request as described in [GNAP] and presenting the existing access token's value in the "existing_access_token" field. ๐ซถ
Since the RS is acting as a client instance, the RS MUST identify itself with its own key in the client field and sign the request just like any client instance would, as described in Section 3.2. The AS MUST determine that the token being presented is appropriate for use at the RS making the token chaining request. Main character rules, no exceptions ๐
POST /tx HTTP/1.1
Host: server.example.com
Content-Type: application/json
Detached-JWS: ejy0...
{
"access_token": {
"access": [
{
"actions": [
"read",
"write",
"dolphin"
],
"locations": [
"https://server.example.net/",
"https://resource.local/other"
],
"datatypes": [
"metadata",
"images"
]
},
"dolphin-metadata"
]
},
"client": "7C7C4AZ9KHRS6X63AJAO",
"existing_access_token": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0"
}
The AS responds with a token for the downstream RS2 as described in [GNAP]. The downstream RS2 can repeat this process as often as needed to call further RSs. Chain reaction, but make it cute ๐
5. IANA Considerations ๐ง
IANA has added values to existing registries and created five registries under the "Grant Negotiation and Authorization Protocol (GNAP)" registry group. Lots of paperwork, but it's all for the good ๐
5.1. Well-Known URIs ๐ธ
The "gnap-as-rs" URI suffix is registered in the "Well-Known URIs" registry to support RS-facing discovery of the AS. ๐
URI Suffix: gnap-as-rs
Change Controller: IETF
Specification Document: Section 3.1 of RFC 9767
Status: Permanent
5.2. GNAP Grant Request Parameters ๐
The following parameter is registered in the "GNAP Grant Request Parameters" registry:
Name: existing_access_token
Type: string
Reference: Section 4 of RFC 9767
5.3. GNAP Token Formats ๐
This document defines a GNAP token format, and IANA has created and maintains a new registry for it called "GNAP Token Formats". Initial values for this registry are given in Section 5.3.2. Future assignments and modifications to existing assignments are made through the Specification Required registration policy [RFC8126]. ๐
The designated expert (DE) is expected to ensure that:
- all registrations follow the template presented in Section 5.3.1.
- the format's definition is sufficiently unique from other formats provided by existing parameters.
- the format's definition specifies the format of the access token in sufficient detail to allow the AS and RS to communicate the token information.
5.3.1. Registry Template ๐
Name: The name of the format.
Status: Whether or not the format is in active use. Possible values
are Active and Deprecated.
Description: The human-readable description of the access token
format.
Reference: The specification that defines the token format.
5.3.2. Initial Registry Contents โจ
+===============+========+====================+============+
| Name | Status | Description | Reference |
+===============+========+====================+============+
| jwt-signed | Active | JSON Web Token, | [JWT] |
| | | signed with JWS | |
+---------------+--------+--------------------+------------+
| jwt-encrypted | Active | JSON Web Token, | [JWT] |
| | | encrypted with JWE | [JWT] |
+---------------+--------+--------------------+------------+
| macaroon | Active | Macaroon | [MACAROON] |
+---------------+--------+--------------------+------------+
| biscuit | Active | Biscuit | [BISCUIT] |
+---------------+--------+--------------------+------------+
| zcap | Active | ZCAP | [ZCAPLD] |
+---------------+--------+--------------------+------------+
Table 1: Initial Contents of the GNAP Token Formats Registry
5.4. GNAP Token Introspection Request ๐ฆ
This document defines GNAP token introspection, and IANA has created and maintains a new registry for it called "GNAP Token Introspection Request". Initial values for this registry are given in Section 5.4.2. Future assignments and modifications to existing assignments are made through the Specification Required registration policy [RFC8126]. ๐
The DE is expected to ensure that:
- all registrations follow the template presented in Section 5.4.1.
- the claim's definition is sufficiently orthogonal to other claims defined in the registry so as to avoid overlapping functionality.
- the claim's definition specifies the syntax and semantics of the claim in sufficient detail to allow the AS and RS to communicate the token values.
5.4.1. Registry Template ๐ท
Name: The name of the claim.
Type: The JSON data type of the claim value.
Reference: The specification that defines the claim.
5.4.2. Initial Registry Contents ๐ซง
The table below has the initial contents of the "GNAP Token Introspection Request" registry. ๐
+=================+=================+=========================+
| Name | Type | Reference |
+=================+=================+=========================+
| access_token | string | Section 3.3 of RFC 9767 |
+-----------------+-----------------+-------------------------+
| proof | string | Section 3.3 of RFC 9767 |
+-----------------+-----------------+-------------------------+
| resource_server | object/string | Section 3.3 of RFC 9767 |
+-----------------+-----------------+-------------------------+
| access | array of | Section 3.3 of RFC 9767 |
| | strings/objects | |
+-----------------+-----------------+-------------------------+
Table 2: Initial Contents of the GNAP Token Introspection
Request Registry
5.5. GNAP Token Introspection Response ๐
This document defines GNAP token introspection, and IANA has created and maintains a new registry for it called "GNAP Token Introspection Response". Initial values for this registry are given in Section 5.5.2. Future assignments and modifications to existing assignments are made through the Specification Required registration policy [RFC8126]. ๐
The DE is expected to ensure that:
- all registrations follow the template presented in Section 5.5.1.
- the claim's definition is sufficiently orthogonal to other claims defined in the registry so as to avoid overlapping functionality.
- the claim's definition specifies the syntax and semantics of the claim in sufficient detail to allow the AS and RS to communicate the token values.
5.5.1. Registry Template ๐ญ
Name: The name of the claim.
Type: The JSON data type of the claim value.
Reference: The specification that defines the claim.
5.5.2. Initial Registry Contents โจ
okay so the "GNAP Token Introspection Response" registry starts out with these 11 fields, and they're all pointing at Section 3.3 of RFC 9767 ๐
+=============+==========================+=========================+
| Name | Type | Reference |
+=============+==========================+=========================+
| active | boolean | Section 3.3 of RFC 9767 |
+-------------+--------------------------+-------------------------+
| access | array of strings/objects | Section 3.3 of RFC 9767 |
+-------------+--------------------------+-------------------------+
| key | object/string | Section 3.3 of RFC 9767 |
+-------------+--------------------------+-------------------------+
| flags | array of strings | Section 3.3 of RFC 9767 |
+-------------+--------------------------+-------------------------+
| exp | integer | Section 3.3 of RFC 9767 |
+-------------+--------------------------+-------------------------+
| iat | integer | Section 3.3 of RFC 9767 |
+-------------+--------------------------+-------------------------+
| nbf | integer | Section 3.3 of RFC 9767 |
+-------------+--------------------------+-------------------------+
| aud | string or array of | Section 3.3 of RFC 9767 |
| | strings | |
+-------------+--------------------------+-------------------------+
| sub | string | Section 3.3 of RFC 9767 |
+-------------+--------------------------+-------------------------+
| iss | string | Section 3.3 of RFC 9767 |
+-------------+--------------------------+-------------------------+
| instance_id | string | Section 3.3 of RFC 9767 |
+-------------+--------------------------+-------------------------+
Table 3: Initial Contents of the GNAP Token Introspection
Response Registry
5.6. GNAP Resource Set Registration Request Parameters ๐
this one is for registering a resource set with a GNAP AS ๐๏ธ IANA made a brand new registry called "GNAP Resource Set Registration Request Parameters" for it, and the starter values are in Section 5.6.2. Any future additions or changes go through the Expert Review registration policy [RFC8126] ๐ธ
the DE is expected to make sure that:
- every registration follows the template in Section 5.6.1. ๐
- the parameter is sufficiently orthogonal to the other parameters in the registry, so nothing overlaps in functionality ๐ฆ
- the parameter's definition gives the syntax and semantics in enough detail that the AS and RS can actually communicate the resource set ๐ซถ
5.6.1. Registry Template
- Name: The name of the parameter.
- Type: The JSON data type of the parameter value.
- Reference: The specification that defines the token.
5.6.2. Initial Registry Contents
here's the starter table for the "GNAP Resource Set Registration Request Parameters" registry โจ
+==============================+=================+=============+
| Name | Type | Reference |
+==============================+=================+=============+
| access | array of | Section 3.4 |
| | strings/objects | of RFC 9767 |
+------------------------------+-----------------+-------------+
| resource_server | object/string | Section 3.4 |
| | | of RFC 9767 |
+------------------------------+-----------------+-------------+
| token_formats_supported | array of | Section 3.4 |
| | strings | of RFC 9767 |
+------------------------------+-----------------+-------------+
| token_introspection_required | boolean | Section 3.4 |
| | | of RFC 9767 |
+------------------------------+-----------------+-------------+
Table 4: Initial Contents of the GNAP Resource Set
Registration Request Parameters Registry
5.7. GNAP Resource Set Registration Response Parameters ๐ง
same energy as the request side, just for the response ๐ IANA made a registry called "GNAP Resource Set Registration Response Parameters" and its starter values are in Section 5.7.2. Future changes go through the Expert Review registration policy [RFC8126] โจ
the DE is expected to make sure that:
- every registration follows the template in Section 5.7.1. ๐
- the parameter is sufficiently orthogonal to the other claims in the registry, so nothing overlaps in functionality ๐ฆ
- the definition gives the syntax and semantics of the claim in enough detail that the AS and RS can communicate the resource set ๐ซถ
5.7.1. Registry Template
- Name: The name of the parameter.
- Type: The JSON data type of the parameter value.
- Reference: The specification that defines the parameter.
5.7.2. Initial Registry Contents
the starter table for the "GNAP Resource Set Registration Response Parameters" registry looks like this ๐
+========================+========+=========================+
| Name | Type | Reference |
+========================+========+=========================+
| resource_reference | string | Section 3.4 of RFC 9767 |
+------------------------+--------+-------------------------+
| instance_id | string | Section 3.4 of RFC 9767 |
+------------------------+--------+-------------------------+
| introspection_endpoint | string | Section 3.4 of RFC 9767 |
+------------------------+--------+-------------------------+
Table 5: Initial Contents of the GNAP Resource Set
Registration Response Parameters Registry
5.8. GNAP RS-Facing Discovery Document Fields ๐
this section is about how a GNAP RS finds a GNAP AS ๐บ๏ธ IANA created a registry called "GNAP RS-Facing Discovery Document Fields" for it, with starter values in Section 5.8.2. Future changes go through the Expert Review registration policy [RFC8126] ๐
the DE is expected to make sure that:
- every registration follows the template in Section 5.8.1. ๐
- the field is sufficiently orthogonal to the other fields in the registry, so nothing overlaps in functionality ๐ฆ
- the field's definition gives the syntax and semantics in enough detail that the RS can talk to the AS ๐ซถ
5.8.1. Registry Template
- Name: The name of the field.
- Type: The JSON data type of the field value.
- Reference: The specification that defines the field.
5.8.2. Initial Registry Contents
the starter table for the "GNAP RS-Facing Discovery Document Fields" registry ๐
+================================+==========+=============+
| Name | Type | Reference |
+================================+==========+=============+
| introspection_endpoint | string | Section 3.1 |
| | | of RFC 9767 |
+--------------------------------+----------+-------------+
| token_formats_supported | array of | Section 3.1 |
| | strings | of RFC 9767 |
+--------------------------------+----------+-------------+
| resource_registration_endpoint | string | Section 3.1 |
| | | of RFC 9767 |
+--------------------------------+----------+-------------+
| grant_request_endpoint | string | Section 3.1 |
| | | of RFC 9767 |
+--------------------------------+----------+-------------+
| key_proofs_supported | array of | Section 3.1 |
| | strings | of RFC 9767 |
+--------------------------------+----------+-------------+
Table 6: Initial Contents of the GNAP RS-Facing
Discovery Document Fields Registry
5.9. GNAP RS-Facing Error Codes ๐จ
this section defines a set of errors the AS can send back to the RS ๐ IANA made a new registry called "GNAP RS-Facing Error Codes" for them, and the starter values are in Section 5.9.2. Future additions or changes go through the Specification Required registration policy [RFC8126] โจ
the DE is expected to make sure that:
- every registration follows the template in Section 5.9.1. ๐
- the error response is unique enough from other errors to give the client instance something actionable ๐ฏ
- the definition of the error response spells out every condition that causes it to be returned, plus what the client instance is expected to do next ๐ซถ
5.9.1. Registration Template
- Error: A unique string code for the error.
- Reference: Reference to the document(s) that specifies the value, preferably including a URI that can be used to retrieve a copy of the document(s). An indication of the relevant sections may also be included but is not required.
5.9.2. Initial Contents
here are the starter error codes ๐ธ
+=========================+=========================+
| Error | Reference |
+=========================+=========================+
| invalid_request | Section 3.5 of RFC 9767 |
+-------------------------+-------------------------+
| invalid_resource_server | Section 3.5 of RFC 9767 |
+-------------------------+-------------------------+
| invalid_access | Section 3.5 of RFC 9767 |
+-------------------------+-------------------------+
Table 7: Initial Contents of the GNAP RS-Facing
Error Codes Registry
6. Security Considerations ๐
this part is serious business, bestie ๐ญ implementers are strongly encouraged to think about the extra security considerations below, on top of the normative requirements in this document and in [GNAP], when they build and deploy GNAP.
6.1. TLS Protection in Transit ๐
every GNAP request sent over untrusted network connections has to go over TLS, as outlined in [BCP195] ๐ก๏ธ that's how the contents of the request and response stay safe from being messed with or intercepted by an attacker ๐ this covers every request from a client instance to the RS, and every request from the RS to an AS. no exceptions, periodt ๐
6.2. Token Validation ๐
Okay bestie, the RS (resource server) has to check the incoming access token in a way that actually fits how it's deployed ๐ซถ. For self-contained stateless tokens like the ones in Section 2.2, that means things like checking the token's signature and making sure the relevant fields and results fit the request being made ๐. For reference-style tokens, or tokens that are otherwise opaque to the RS, the token introspection RS-facing API can be used to get updated info about the token's state, as described in Section 3.3 ๐ฆ.
The RS needs to validate that a token: โจ
- is intended for this RS (audience restriction) ๐ฏ
- is presented using the appropriate key for the token (see also Section 6.4) ๐
- identifies an appropriate subject to access the resource (usually this is the resource owner who authorized the token's issuance) ๐
- is issued by a trusted AS for this resource ๐ธ
Even though key proofing mechanisms have to cover the value of the token, validating the key proof alone is not enough to protect a request to an RS ๐.
Bestie, this is the big one. If an RS only checks the presentation method (see Section 6.4) and never validates the token itself, an attacker could use a compromised key or a confused deputy to make arbitrary calls to the RS, way beyond what the RO actually authorized ๐.
6.3. Caching Token Validation Result ๐ง
Token validation can be an expensive process, since it needs either cryptographic operations or network calls to an introspection service as described in Section 3.3. So an RS could cache the results of token validation for a particular token ๐. The trade-off here is a seriously important decision for implementers. Relying on a cached validation result boosts performance and lowers processing overhead, but it costs you some liveness and accuracy in the cache ๐ญ. While a cached value is in use at the RS, an attacker could present a revoked token and have it accepted ๐.
As with any cache, its consistency can be managed in a bunch of different ways ๐. One of the simplest methods is managing the lifetime of the cache so you can balance performance and security โ๏ธ. If the cache is too long, an attacker gets a bigger window to use a revoked token ๐ โโ๏ธ. If the window is too short, you lose most of the benefits of using the cache ๐ค. It's also possible for an AS to send a proactive signal to the RS to invalidate revoked access tokens, though that kind of mechanism is outside the scope of this specification ๐.
6.4. Key Proof Validation ๐
For key-bound access tokens, the proofing method needs to be validated alongside the value of the token itself, as described in Section 6.2. The validation process is defined by the key proofing method, as described in Section 7.3 of [GNAP] โจ.
If the proofing method isn't validated, an attacker could use a compromised token without having access to the token's bound key ๐ฑ.
The RS also needs to make sure the proofing method is appropriate for the key associated with the token, including any choice of algorithm or identifiers ๐ง.
The proofing should be validated independently on each request to the RS, especially since aspects of the call could vary from one to the next ๐. So the RS should never cache the results of a proof validation from one message and apply them to a later message ๐ โโ๏ธ๐.
6.5. Token Exfiltration ๐ต๏ธโโ๏ธ
The RS sees the token value, which means a compromised RS could leak that value to an attacker ๐จ. So the RS needs to treat token values as sensitive information and protect them from exfiltration ๐.
This is extra scary with bearer tokens and tokens bound to a shared key, because the RS has everything it needs to create a brand new, valid request using that token ๐.
6.6. Token Reuse by an RS ๐
If the access token is a bearer token, or the RS has access to the key material needed to present the token, the RS could be tricked into reusing an access token that a client presented to it ๐ฌ. Sure, it's possible to build a system that makes use of this as a feature, but it's safer to exchange the incoming access token for another contextual token for the RS to use, as described in Section 4 ๐ . That token can be bound to the RS's own keys and limited to just the access the RS needs, instead of the full set of rights tied to the token issued to the client instance ๐ซถ.
6.7. Token Format Considerations ๐ฆ
With formatted tokens, the format itself is likely to come with its own considerations, and the RS needs to follow any of those during the token validation process ๐งพ. How those considerations apply is specific to the format and outside the scope of this specification ๐ธ.
6.8. Oversharing Token Contents ๐คซ
The contents of the access token model reveal information about the token's context and rights to the RS ๐. That's true whether the contents are parsed from the token itself or sent in an introspection response ๐จ.
Every RS probably doesn't need to know every detail of the token model, especially in systems where one access token works across multiple RSs ๐. An attacker could use that to learn about the bigger system by compromising just one RS ๐ฑ. So limit the info to only what's relevant to a specific RS, like using a limited introspection reply as defined in Section 3.3, and the system can follow the principle of least disclosure for each RS ๐ฆโจ.
6.9. Resource References ๐๏ธ
Resource references, the ones returned by the protocol in Section 3.4, are meant to be opaque to both the RS and the client ๐ซฃ. But since the AS controls them, the AS can put whatever content it wants into the reference value ๐คทโโ๏ธ. That value could accidentally reveal system structure or other internal details if an unintended party processes it ๐ฌ. On top of that, these patterns could make client software and RSs depend on certain structures being present in the reference value, which weakens the separation of concerns between the different roles in a GNAP system ๐.
To fix this, the AS should only use fully random or encrypted values for resource references ๐๐.
6.10. Token Reissuance from an Untrusted AS ๐จ
It's possible for an attacker's client instance to issue its own tokens to another client instance, pretending to be an AS that the second client instance has chosen to trust ๐ฅท. If the token is a bearer token, or the reissuance is bound using an AS-provided key, the target client instance won't be able to tell that the token was originally issued by the valid AS ๐ต. That lets an attacker slide their own session and rights into an unsuspecting client instance, disguised as a valid token that looks like it was issued to the target client on behalf of its own RO ๐.
This attack depends on a misconfiguration in the targeted client. That client has been set up to get tokens from the attacker's AS and use them with the target RS, even though the target RS has no association with the attacker's AS ๐. But since the token is ultimately coming from the trusted AS and is being presented with a valid key, the RS has no way of knowing the token passed through an intermediary ๐ซ .
To mitigate this, the RS can publish its association with the trusted AS through either discovery or documentation ๐ข. That way, a client that properly follows this association will only go directly to the trusted AS for access tokens for the RS โ .
Also, limiting bearer tokens and AS-provided keys to only highly trusted ASs in certain circumstances stops the attacker from willingly exfiltrating their token to an unsuspecting client instance ๐ก๏ธ.
6.11. Introspection of Token Keys ๐
The introspection response defined in Section 3.3 gives the AS a way to tell the RS what key material is needed to validate the key proof of the request ๐ฌ. If someone captures the introspection response, they can expose these security keys to an attacker ๐ฑ. With asymmetric cryptography, only the public key is exposed, so the token can't be reused by the attacker based on that result alone ๐. That said, it could still reveal info about the client instance that wasn't known before ๐.
If an access token is bound to a symmetric key, the RS needs the full key value to validate the key proof of the request, as described in Section 6.4 ๐๏ธ. But handing the key material to the RS also gives the RS the ability to create a new request with the token ๐ฌ. In that case, the RS faces similar risks of token exfiltration and reuse as with a bearer token, as described in Section 6.6 ๐. So symmetric keys should only be used in systems where the RS can be fully trusted not to create a new request with tokens presented to it ๐ซถ.
6.12. RS Registration and Management ๐
Most functions of the RS-facing API in Section 3 are protected by requiring the RS to present proof of a signing key along with the request ๐๏ธ. That identifies the RS making the call, and it can be paired with an AS-specific access token too ๐. This setup lets the AS respond differently to API calls from different RSs ๐ . For example, it can answer introspection calls with only the access rights that are relevant to a given RS, instead of every access right the token could be good for ๐.
How an RS and its keys become known to the AS is out of scope for this specification ๐. Still, the expected common practice is to statically register an RS, so it can protect specific resources or certain classes of resources ๐. Fundamentally, an RS can only offer the resources that it serves ๐ฐ. But a rogue AS could try to register a set of resources that mimics a different RS, just to get an access token that's usable at the target RS ๐. If the access token is a bearer token, or it's bound to a symmetric key that the RS knows, the attacker's RS gets the ability and knowledge to use that token elsewhere ๐จ.
In some ecosystems, dynamic registration of an RS and its associated resources is doable ๐ง. In those systems, the RS's identity could be conveyed by a URI passed in the location field of an access rights request, which lets the AS limit the view the RS has of the larger system ๐ฆ.
7. Privacy Considerations โจ
7.1. Token Contents โจ
okay bestie, the access token can carry personal info about the end user, the RO, or other parties ๐ฅบ and that's true whether the RS reads it straight out of the token or gets it from an introspection response ๐
sometimes an RS genuinely needs that info to do its job โจ but a lot of the time it only sees those details by accident, not on purpose ๐ imagine a client holding an access token that works for both medical and non-medical APIs ๐ฉบ. if that token has a medical record number in it so the medical RS can do its thing, then the RS for a non-medical API ends up learning the user's medical record number too ๐ญ even though that API has zero reason to connect the dots.
to fix this, a formatted token can have separate sections aimed at different RSs so each one only gets its own data ๐. or you can use token introspection to limit what data comes back to each RS, as defined in Section 3.3 ๐ซถ
7.2. Token Use Disclosure through Introspection ๐ฆ
when an RS uses introspection, the AS finds out that a certain token is being used at a certain RS ๐. if the RS is a separate system, the AS wouldn't have any other way to see that action. so the AS could start figuring out the patterns and habits of particular end users just by watching which RSs get hit and when ๐ณ that's a whole privacy moment, not gonna lie.
7.3. Mapping a User to an AS ๐ง
when a client instance gets info about the protecting AS from an RS, that info can be used to learn stuff about the protected resources without the resources actually being released ๐ต๏ธโโ๏ธ. for example, say a medical record is protected by a personal AS. an untrusted client could call an RS to find out where the AS protecting that record lives ๐บ๏ธ. since that AS is tightly tied to one single RO, the untrusted and unauthorized client software can pick up info about the resource without ever touching the record itself. not me noticing this as a huge red flag ๐ฉ๐
8. References ๐
8.1. Normative References ๐
- [BCP195] Best Current Practice 195,
https://www.rfc-editor.org/info/bcp195. At the time of writing, this BCP comprises the following:- Moriarty, K. and S. Farrell, "Deprecating TLS 1.0 and TLS 1.1", BCP 195, RFC 8996, DOI 10.17487/RFC8996, March 2021,
https://www.rfc-editor.org/info/rfc8996. - Sheffer, Y., Saint-Andre, P., and T. Fossati, "Recommendations for Secure Use of Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS)", BCP 195, RFC 9325, DOI 10.17487/RFC9325, November 2022,
https://www.rfc-editor.org/info/rfc9325.
- Moriarty, K. and S. Farrell, "Deprecating TLS 1.0 and TLS 1.1", BCP 195, RFC 8996, DOI 10.17487/RFC8996, March 2021,
- [GNAP] Richer, J., Ed. and F. Imbault, "Grant Negotiation and Authorization Protocol (GNAP)", RFC 9635, DOI 10.17487/RFC9635, October 2024,
https://www.rfc-editor.org/info/rfc9635. - [JWT] Jones, M., Bradley, J., and N. Sakimura, "JSON Web Token (JWT)", RFC 7519, DOI 10.17487/RFC7519, May 2015,
https://www.rfc-editor.org/info/rfc7519. - [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997,
https://www.rfc-editor.org/info/rfc2119. - [RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform Resource Identifier (URI): Generic Syntax", STD 66, RFC 3986, DOI 10.17487/RFC3986, January 2005,
https://www.rfc-editor.org/info/rfc3986. - [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017,
https://www.rfc-editor.org/info/rfc8174. - [RFC8259] Bray, T., Ed., "The JavaScript Object Notation (JSON) Data Interchange Format", STD 90, RFC 8259, DOI 10.17487/RFC8259, December 2017,
https://www.rfc-editor.org/info/rfc8259. - [RFC8792] Watsen, K., Auerswald, E., Farrel, A., and Q. Wu, "Handling Long Lines in Content of Internet-Drafts and RFCs", RFC 8792, DOI 10.17487/RFC8792, June 2020,
https://www.rfc-editor.org/info/rfc8792.
8.2. Informative References ๐ธ
- [BISCUIT] Biscuit, "Biscuit Authorization",
https://www.biscuitsec.org/. - [MACAROON] Birgisson, A., Politz, J. G., Erlingsson, U., Taly, A., Vrable, M., and M. Lentczner, "Macaroons: Cookies with Contextual Caveats for Decentralized Authorization in the Cloud", NDSS Symposium 2014, DOI 10.14722/ndss.2014.23212, February 2014,
https://www.ndss-symposium.org/ndss2014/ndss-2014-programme/macaroons-cookies-contextual-caveats-decentralized-authorization-cloud/. - [RFC8126] Cotton, M., Leiba, B., and T. Narten, "Guidelines for Writing an IANA Considerations Section in RFCs", BCP 26, RFC 8126, DOI 10.17487/RFC8126, June 2017,
https://www.rfc-editor.org/info/rfc8126. - [ZCAPLD] Lemmer-Webber, C., Ed. and M. Sporny, Ed., "Authorization Capabilities for Linked Data v0.3", W3C Draft Community Group Report, January 2023,
https://w3c-ccg.github.io/zcap-spec/.
Acknowledgements ๐ซถ๐
The editors would like to thank the following individuals for their reviews, feedback, implementations, and contributions: Aaron Parecki, Adrian Gropper, Andrii Deinega, Annabelle Backman, Dmitry Barinov, Fabien Imbault, Florian Helmschmidt, George Fletcher, Justin Richer, Kathleen Moriarty, Leif Johansson, Mike Varley, Nat Sakimura, Takahiko Kawasaki, and Yaron Sheffer. ๐
Additionally, the editors want to acknowledge the immense contributions of Aaron Parecki to the content of this document. We thank him for his insight, input, and hard work, without which GNAP would not have grown to what it is. ๐โจ
Authors' Addresses ๐
Justin Richer (editor)
Bespoke Engineering
Email: ietf@justin.richer.org
URI: https://bspk.io/
Fabien Imbault
acert.io
Email: fabien.imbault@acert.io
URI: https://acert.io/
๐ฆ where this page comes from
- ietf: rfc9767 2025-04
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms