RFC 9635
Grant Negotiation and Authorization Protocol (GNAP)
Grant Negotiation and Authorization Protocol (GNAP) ✨
RFC 9635 🎀 is the spec for GNAP, the Grant Negotiation and Authorization Protocol. It's an Internet Standards Track document from the IETF, so it's officially a real standard. It was published in October 2024 and carries ISSN 2070-1721. The authors are J. Richer, Ed. (editor) from Bespoke Engineering and F. Imbault from acert.io. The title block doesn't list any RFCs that this one obsoletes or updates, so it's not replacing or patching anything else. It's a fresh start 💅
Abstract
okay bestie, the abstract is basically the TL;DR 🦋 GNAP gives you a way to delegate authorization to a piece of software, which means letting an app do stuff on your behalf. It also gives the software the results and artifacts of that delegation, so it knows what it was actually given. The delegation can cover access to a set of APIs and also subject information that gets handed straight to the software. Main character energy, honestly 👑
Status of This Memo
this section is the official paperwork, but make it cute 🧚 It's an Internet Standards Track document. The IETF made it, and it reflects what the IETF community agreed on. It went through public review and got approved for publication by the Internet Engineering Steering Group (IESG). If you want more about Internet Standards, check Section 2 of RFC 7841. Info about the current status of this document, any errata, and how to send feedback is at https://www.rfc-editor.org/info/rfc9635 🌸
Copyright Notice
more legal stuff, but it's giving important 🙈 The copyright belongs to the IETF Trust and the people listed as document authors, and it's Copyright (c) 2024. All rights are reserved. This document falls under BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents, which you can find at https://trustee.ietf.org/license-info as they stood when this was published. Read those carefully, because they spell out your rights and restrictions. Any Code Components pulled out of this document have to include Revised BSD License text, as described in Section 4.e of the Trust Legal Provisions. They also come with no warranty, which is also described in the Revised BSD License 🔥
Table of Contents
okay here's the full map of the whole spec, so you can see where everything lives ✨
1. Introduction
1.1. Terminology
1.2. Roles
1.3. Elements
1.4. Trust Relationships
1.5. Protocol Flow
1.6. Sequences
1.6.1. Overall Protocol Sequence
1.6.2. Redirect-Based Interaction
1.6.3. User Code Interaction
1.6.4. Asynchronous Authorization
1.6.5. Software-Only Authorization
1.6.6. Refreshing an Expired Access Token
1.6.7. Requesting Subject Information Only
1.6.8. Cross-User Authentication
2. Requesting Access
2.1. Requesting Access to Resources
2.1.1. Requesting a Single Access Token
2.1.2. Requesting Multiple Access Tokens
2.2. Requesting Subject Information
2.3. Identifying the Client Instance
2.3.1. Identifying the Client Instance by Reference
2.3.2. Providing Displayable Client Instance Information
2.3.3. Authenticating the Client Instance
2.4. Identifying the User
2.4.1. Identifying the User by Reference
2.5. Interacting with the User
2.5.1. Start Mode Definitions
2.5.2. Interaction Finish Methods
2.5.3. Hints
3. Grant Response
3.1. Request Continuation
3.2. Access Tokens
3.2.1. Single Access Token
3.2.2. Multiple Access Tokens
3.3. Interaction Modes
3.3.1. Redirection to an Arbitrary URI
3.3.2. Launch of an Application URI
3.3.3. Display of a Short User Code
3.3.4. Display of a Short User Code and URI
3.3.5. Interaction Finish
3.4. Returning Subject Information
3.4.1. Assertion Formats
3.5. Returning a Dynamically Bound Client Instance Identifier
3.6. Error Response
4. Determining Authorization and Consent
4.1. Starting Interaction with the End User
4.1.1. Interaction at a Redirected URI
4.1.2. Interaction at the Static User Code URI
4.1.3. Interaction at a Dynamic User Code URI
4.1.4. Interaction through an Application URI
4.2. Post-Interaction Completion
4.2.1. Completing Interaction with a Browser Redirect to the
Callback URI
4.2.2. Completing Interaction with a Direct HTTP Request
Callback
4.2.3. Calculating the Interaction Hash
5. Continuing a Grant Request
5.1. Continuing after a Completed Interaction
5.2. Continuing during Pending Interaction (Polling)
5.3. Modifying an Existing Request
5.4. Revoking a Grant Request
6. Token Management
6.1. Rotating the Access Token Value
6.1.1. Binding a New Key to the Rotated Access Token
6.2. Revoking the Access Token
7. Securing Requests from the Client Instance
7.1. Key Formats
7.1.1. Key References
7.1.2. Key Protection
7.2. Presenting Access Tokens
7.3. Proving Possession of a Key with a Request
7.3.1. HTTP Message Signatures
7.3.2. Mutual TLS
7.3.3. Detached JWS
7.3.4. Attached JWS
8. Resource Access Rights
8.1. Requesting Resources by Reference
9. Discovery
9.1. RS-First Method of AS Discovery
9.2. Dynamic Grant Endpoint Discovery
10. IANA Considerations
10.1. HTTP Authentication Scheme Registration
10.2. Media Type Registration
10.2.1. application/gnap-binding-jwsd
10.2.2. application/gnap-binding-jws
10.2.3. application/gnap-binding-rotation-jwsd
10.2.4. application/gnap-binding-rotation-jws
10.3. GNAP Grant Request Parameters
10.3.1. Registration Template
10.3.2. Initial Contents
10.4. GNAP Access Token Flags
10.4.1. Registration Template
10.4.2. Initial Contents
10.5. GNAP Subject Information Request Fields
10.5.1. Registration Template
10.5.2. Initial Contents
10.6. GNAP Assertion Formats
10.6.1. Registration Template
10.6.2. Initial Contents
10.7. GNAP Client Instance Fields
10.7.1. Registration Template
10.7.2. Initial Contents
10.8. GNAP Client Instance Display Fields
10.8.1. Registration Template
10.8.2. Initial Contents
10.9. GNAP Interaction Start Modes
10.9.1. Registration Template
10.9.2. Initial Contents
10.10. GNAP Interaction Finish Methods
10.10.1. Registration Template
10.10.2. Initial Contents
10.11. GNAP Interaction Hints
10.11.1. Registration Template
10.11.2. Initial Contents
10.12. GNAP Grant Response Parameters
10.12.1. Registration Template
10.12.2. Initial Contents
10.13. GNAP Interaction Mode Responses
10.13.1. Registration Template
10.13.2. Initial Contents
10.14. GNAP Subject Information Response Fields
10.14.1. Registration Template
10.14.2. Initial Contents
10.15. GNAP Error Codes
10.15.1. Registration Template
10.15.2. Initial Contents
10.16. GNAP Key Proofing Methods
10.16.1. Registration Template
10.16.2. Initial Contents
10.17. GNAP Key Formats
10.17.1. Registration Template
10.17.2. Initial Contents
10.18. GNAP Authorization Server Discovery Fields
10.18.1. Registration Template
10.18.2. Initial Contents
11. Security Considerations
11.1. TLS Protection in Transit
11.2. Signing Requests from the Client Software
11.3. MTLS Message Integrity
11.4. MTLS Deployment Patterns
11.5. Protection of Client Instance Key Material
11.6. Protection of Authorization Server
11.7. Symmetric and Asymmetric Client Instance Keys
11.8. Generation of Access Tokens
11.9. Bearer Access Tokens
11.10. Key-Bound Access Tokens
11.11. Exposure of End-User Credentials to Client Instance
11.12. Mixing Up Authorization Servers
11.13. Processing of Client-Presented User Information
11.14. Client Instance Pre-registration
11.15. Client Instance Impersonation
11.16. Client-Hosted Logo URI
11.17. Interception of Information in the Browser
11.18. Callback URI Manipulation
11.19. Redirection Status Codes
11.20. Interception of Responses from the AS
11.21. Key Distribution
11.22. Key Rotation Policy
11.23. Interaction Finish Modes and Polling
11.24. Session Management for Interaction Finish Methods
11.25. Calculating Interaction Hash
11.26. Storage of Information during Interaction and Continuation
11.27. Denial of Service (DoS) through Grant Continuation
11.28. Exhaustion of Random Value Space
11.29. Front-Channel URIs
11.30. Processing Assertions
11.31. Stolen Token Replay
11.32. Self-Contained Stateless Access Tokens
11.33. Network Problems and Token and Grant Management
11.34. Server-Side Request Forgery (SSRF)
11.35. Multiple Key Formats
11.36. Asynchronous Interactions
11.37. Compromised RS
11.38. AS-Provided Token Keys
12. Privacy Considerations
12.1. Surveillance
12.1.1. Surveillance by the Client
12.1.2. Surveillance by the Authorization Server
12.2. Stored Data
12.3. Intrusion
12.4. Correlation
12.4.1. Correlation by Clients
12.4.2. Correlation by Resource Servers
12.4.3. Correlation by Authorization Servers
12.5. Disclosure in Shared References
13. References
13.1. Normative References
13.2. Informative References
Appendix A. Comparison with OAuth 2.0
Appendix B. Example Protocol Flows
B.1. Redirect-Based User Interaction
B.2. Secondary Device Interaction
B.3. No User Involvement
B.4. Asynchronous Authorization
B.5. Applying OAuth 2.0 Scopes and Client IDs
Appendix C. Interoperability Profiles
C.1. Web-Based Redirection
C.2. Secondary Device
Appendix D. Guidance for Extensions
Appendix E. JSON Structures and Polymorphism
Acknowledgements
Authors' Addresses
- Introduction ✨
okay bestie, so GNAP lets a piece of software (the client instance) ask for delegated authorization to resource servers and subject information 💖 The delegated access to the resource server lets the client instance reach resources and APIs on behalf of a resource owner, and delegated access to subject information can then be used by the client instance to make authentication decisions 🔐 This delegation is set up by an authorization server, usually on behalf of a resource owner. The end user running the software can talk to the authorization server to authenticate, give consent, and authorize the request as a resource owner 🎀
The process where the delegation happens is called a grant, and GNAP lets the grant process be negotiated over time by multiple parties in distinct roles 🦋
This spec focuses on the part of the delegation process that faces the client instance 💅 It defines interoperable ways for a client instance to request, negotiate, and receive access to information the authorization server makes available. It also defines ways for the client instance to access protected resources at a resource server ✨ There's a section on discovery too, so the client instance can configure itself dynamically 🧚 How an authorization server and resource server interoperate is covered in [GNAP-RS] 💜
The whole point of this protocol is interoperability between the different parties in each role, not dictating how each one is implemented 🌸 Where it's useful, GNAP may make recommendations about internal implementation details, but those are there to keep the overall deployment secure, not to be prescriptive about the implementation 🫶
GNAP solves a lot of the same use cases as OAuth 2.0 [RFC6749], OpenID Connect [OIDC], and the whole family of protocols that grew up around that ecosystem 👑 BUT (sorry, not sorry) GNAP is not an extension of OAuth 2.0 and isn't meant to be directly compatible with it 😭 It's built to give functionality and handle use cases that OAuth 2.0 can't easily or cleanly address 💅 Appendix A goes deeper on the protocol rationale compared to OAuth 2.0 🎀 GNAP and OAuth 2.0 will probably exist side by side for lots of deployments, so the designers put thought into making it easy to map and transition from existing OAuth 2.0 systems to GNAP 🦋 Some examples are in Appendix B.5 ✨
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 document has non-normative examples of partial and complete HTTP messages, JSON structures, URIs, query components, keys, and other elements 🍓 Whenever possible, it uses "URI" as a generic term, since that lines up with the recommendations in [RFC3986] and better fits the idea that the identifier might be reachable through various/generic means (compared to URLs) 🧚 Some examples use a single trailing backslash () to show line wrapping for long values, as per [RFC8792] 💅 The \ character and the leading spaces on wrapped lines are not part of the value ⭐
This document uses the term "mutual TLS" as defined by [RFC8705] 🔥 The shortened form "MTLS" means the same thing 💜
For brevity, the word "signature" on its own refers to both digital signatures (asymmetric cryptography) and keyed Message Authentication Codes (MACs) (symmetric cryptography) 🫶 Similarly, the verb "sign" means generating either a digital signature or a keyed MAC over a given signature base 🦋 The qualified term "digital signature" refers specifically to the output of an asymmetric cryptographic signing operation 👑
1.2. Roles 🎀
The parties in GNAP perform actions under different roles 💅 Roles are defined by the actions taken and the expectations the overall protocol places on the role 🌸
+-------------+ +------------+
| | | |
|Authorization| | Resource |
| Server | | Server |
| |<--+ +--->| |
+-----+-------+ | | +------------+
║ | |
║ +--+---+---+
║ | Client |
║ | Instance |
║ +----+-----+
║ ║
.----+----. ║ .----------.
| | +=====+ |
| Resource | | End |
| Owner | ~ ~ ~ ~ ~ ~ | User |
| | | |
`---------` `----------`
Legend:
===== indicates interaction between a human and computer
----- indicates interaction between two pieces of software
~ ~ ~ indicates a potential equivalence or out-of-band
communication between roles
Figure 1: Roles in GNAP
Authorization Server (AS): Server that grants delegated privileges to a particular instance of client software in the form of access tokens or other information (such as subject information) 🌈 The AS is uniquely defined by the grant endpoint URI, which is the absolute URI where grant requests are started by clients ✨
Client: Application that consumes resources from one or several resource servers, possibly needing access privileges from one or several ASes 💖 The client is operated by the end user, or it runs autonomously on behalf of a resource owner 🧚
For example, a client can be a mobile application, a web application, a backend data processor, etc. 🍓
Note: This specification differentiates between a specific instance (the client instance, identified by its unique key) and the software running the instance (the client software) 💅 For some kinds of client software, there could be many instances of that software, each instance with a different key 🫶
Resource Server (RS): Server that provides an API on protected resources, where operations on the API require a valid access token issued by a trusted AS 🔥
Resource Owner (RO): Subject entity that may grant or deny operations on resources it has authority over 👑
Note: Granting or denying an operation may be manual (i.e., through an interaction with a physical person) or automatic (i.e., through predefined organizational rules) 🦋
End user: Natural person that operates a client instance 🎀
Note: That natural person may or may not be the same entity as the RO 🌸
The design of GNAP doesn't assume any one deployment architecture 💅 Instead it defines roles that can be fulfilled in a number of different ways for different use cases 🌈 As long as a given role fulfills all of its obligations and behaviors as defined by the protocol, GNAP doesn't add any extra requirements on its structure or setup ✨
Multiple roles can be fulfilled by the same party, and a given party can switch roles in different instances of the protocol 💖 For example, in lots of cases the RO and end user are the same person, where a user authorizes the client instance to act on their own behalf at the RS 🧚 In that case, one party fills both the RO and end user roles, but the roles are still defined separately so other use cases can have them fulfilled by different parties 🍓
Another example: in some complex scenarios, an RS receiving requests from one client instance can act as a client instance for a downstream secondary RS in order to fulfill the original request 💅 Here one piece of software is both an RS and a client instance from different perspectives, and it fulfills these roles separately as far as the overall protocol is concerned 🫶
A single role doesn't have to be deployed as one monolithic service 🦋 For example, a client instance could have frontend components installed on the end user's device plus a backend system the frontend talks to 👑 If both of these components take part in the delegation protocol, they're both considered part of the client instance 🎀 If there are several copies of the client software that run separately but all share the same key material, like a deployed cluster, then that cluster is considered a single client instance 🌸 In these cases, the distinct components of what counts as a GNAP client instance may use any number of communication mechanisms between them, all of which are an implementation detail of the client instance and out of scope for GNAP 💅
Another example: an AS could also be built from many constituent components in a distributed architecture 🌈 The component the client instance calls directly could be different from the component the RO interacts with to drive consent, since API calls and user interaction have different security considerations in many environments ✨ Furthermore, the AS might need to collect identity claims about the RO from one system that deals with user attributes, while generating access tokens at another system that deals with security rights 💖 From GNAP's perspective, all of these are pieces of the AS, and together they fulfill the role of the AS as defined by the protocol 🧚 These pieces may have their own internal communication mechanisms, which are out of scope for GNAP 🍓
1.3. Elements 💜
Besides the roles above, the protocol also involves several elements that the roles act upon throughout the process 🫶
Access Token: A data artifact representing a set of rights and/or attributes 🔥
Note: An access token can first be issued to a client instance (requiring authorization by the RO) and then rotated later 👑
Grant: (verb) To permit an instance of client software to receive some attributes at a specific time and with a specific duration of validity, and/or to exercise some set of delegated rights to access a protected resource 🦋 (noun) The act of granting permission to a client instance 🎀
Privilege: Right or attribute associated with a subject 🌸
Note: The RO defines and maintains the rights and attributes associated with the protected resource and might temporarily delegate some set of those privileges to an end user 💅 This process is referred to as "privilege delegation" 🌈
Protected Resource: Protected API that is served by an RS and that can be accessed by a client, if and only if a valid and sufficient access token is provided ✨
Note: To avoid complex sentences, the specification document may simply refer to "resource" instead of "protected resource" 💖
Right: Ability given to a subject to perform a given operation on a resource under the control of an RS 🧚
Subject: Person or organization 🍓 The subject decides whether and under which conditions its attributes can be disclosed to other parties 💅
Subject Information: Set of statements and attributes asserted by an AS about a subject 🫶 These statements can be used by the client instance as part of an authentication decision 🔥
1.4. Trust Relationships ✨
okay bestie, so GNAP's whole trust goal is this: the RO trusts the AS to handle access validation and delegation of protected resources to end users, through third party clients 🫶
that big trust goal can be broken down into smaller trust relationships between the software pieces and the roles. the main pairs are end user/RO, end user/client, client/AS, RS/RO, AS/RO, and AS/RS 💅 trust works like this: one agent trusts another if the other one has been told it promised to follow the protocol in the past (think pre-registration and uncompromised cryptographic components 🔐), or if the trusting side can work it out indirectly (like a compliant client request) 🧠
every agent gets to decide how much its promises are worth, and that valuation is totally its own call 💜 some things that might factor in: the benefits of working with other agents (like safe client access and playing nicely with identity standards 🌈), the cost of following the protocol (including its security and privacy requirements and recommendations), how important each promise is ranked (like a policy the AS decides on 📋), and how risky it feels to not be able to defend against threats 😬 these valuations can also change depending on the context of the request. for example, depending on how GNAP is being used, the AS might choose to use or ignore hints from the client, or the RS might refuse bearer tokens 🙅♀️ and some promises can get affected by what happened before, like repeated requests 🔁
here's the breakdown of each trust relationship, no cap:
end user/RO: this one only exists when the end user and the RO are different people. in that case, the end user needs some out-of-band mechanism to get the RO's consent (see Section 4) 🤝 GNAP generally assumes humans can be authenticated, thanks to identity protocols. for instance, through an id_token assertion as described in Section 2.2 🪪
end user/client: the client is basically acting as a user agent 📱 depending on the technology (browser, single-page application (SPA), mobile application, Internet of Things (IoT) device, etc.), some interactions might or might not be possible (as described in Section 2.5.1) 🤷♀️ client developers implement the requirements and usually some recommendations or best practices, so end users can use their software with confidence 💖 but end users could also end up with an attacker's client software, or a poorly implemented client, without even realizing it 😭
end user/AS: when the client supports the interaction feature (see Section 3.3), the end user talks to the AS through an AS-provided interface ✨ in lots of cases this happens as a front-channel interaction through the end user's browser 🌐 check out Section 11.29 for some considerations on trusting these interactions 👀
client/AS: okay so an honest AS might end up facing an attacker's client (as we just talked about), or the other way around, and GNAP really aims to make common attacks impractical 🛡️ this spec makes access tokens opaque to the client and defines the request/response scheme in detail, which avoids extra trust hypotheses from this super critical piece of software 🙌 still, the AS can define cryptographic attestations or optional rules to make life easier for clients it already trusts, based on past behavior or organizational policies (see Section 2.3) 🎀
RS/RO: on behalf of the RO, the RS promises to protect its resources from unauthorized access, and it only accepts valid access tokens issued by a trusted AS 🏰 if tokens are key bound, proper validation of the proofing method is expected from the RS 🔑
AS/RO: the AS is expected to follow the decisions the RO makes, whether that's through interactive consent requests, repeated interactions, or automated rules (as described in Section 1.6) 📜 privacy considerations aim to lower the risk of an honest but too-curious AS, or the fallout from unexpected user data exposure 🙈
AS/RS: the AS promises to issue valid access tokens to legitimate client requests, meaning it does the appropriate due diligence, as defined in the GNAP 🧾 some optional configurations are covered by [GNAP-RS] 📚
a global assumption GNAP makes is that authorization requests are security and privacy sensitive. the measures for that are detailed in Sections 11 and 12, respectively 🔐💀
a formal trust model is out of scope for this specification, but one could be developed using techniques such as the Promise Theory [promise-theory] 🧠🌸
1.5. Protocol Flow 🦋
GNAP is fundamentally designed to allow delegated access to APIs and other information, such as subject information 🌷 it does this with a multi-stage, stateful process. different parties can feed information into the system, which lets them change and build on the state of the delegated access and its artifacts 🔄
the underlying requested grant moves through several states as different actions happen during the protocol, as shown in Figure 2 🧚
.-----.
| |
+------+--+ | Continue
.---Need Interaction---->| | |
/ | Pending |<--`
/ .--Finish Interaction--+ |
/ / (approve/deny) +----+----+
/ / |
/ / | Cancel
/ v v
+-+----------+ +===========+
| | ║ ║
---Request-->| Processing +------Finalize---->║ Finalized ║
| | ║ ║
+-+----------+ +===========+
\ ^ ^
\ \ | Revoke or
\ \ | Finalize
\ \ +-----+----+
\ `-----Update---------+ |
\ | Approved |<--.
`-----No Interaction--->| | |
+-------+--+ | Continue
| |
`-----`
Figure 2: State Diagram of a Grant Request in GNAP 📊✨
okay so the state of the grant request is defined and managed by the AS 👑, but the client instance also has to keep track of its own view of the grant request over time 🫶. How those roles manage their state is totally outside the scope of this spec, so we're not going there today 🙈.
processing 🔄: when a request for access (Section 2) hits the AS, a brand new grant request gets created and placed in the processing state. This state is also entered when an existing grant request gets updated by the client instance, and when interaction is completed. In this state the AS looks at the context of the grant request to figure out whether it needs to interact with the end user or RO for approval. The grant request has to leave this state before a response can go back to the client instance, no skipping! If approval is needed, the request moves to the pending state, and the AS sends back a continuation response (Section 3.1) along with any interaction responses that fit (Section 3.3). If no approval is needed, like when the client instance is acting on its own behalf or the AS can tell that access has already been fulfilled, the request moves to the approved state, where access tokens for API access (Section 3.2) and subject information (Section 3.4) can be issued to the client instance 💖. And if the AS decides no more processing can happen (like a timeout or an unrecoverable error), the grant request gets moved to the finalized state and is terminated 💀.
pending ⏳: when a request needs approval from an RO, or interaction with the end user is required, the grant request enters pending. In this state, no access tokens can be granted, and no subject information can be released to the client instance. Hard pass on both 🙅♀️. While a grant request is in this state, the AS is busy gathering the consent and authorization (Section 4) for the requested access. A grant request in this state is always tied to a continuation access token bound to the client instance's key (see Section 3.1 for the details on that continuation access token) 🔐. If no interaction finish method (Section 2.5.2) is associated with the request, the client instance can send a polling continuation request (Section 5.2) to the AS. That gets back a continuation response (Section 3.1) while the grant request is still in this state, so the client instance can keep checking on its pending grant request 👀. If an interaction finish method (Section 2.5.2) is specified in the grant request, the client instance can continue the request after interaction (Section 5.1) to the AS, which moves the request to the processing state to get re-evaluated by the AS 🔁. Note that this happens whether the RO approved or denied the grant, since the AS needs the full context of the request before deciding the next step 🧠. When other information shows up in the context of the grant request, like through the asynchronous actions of the RO, the AS moves the request to the processing state to be re-evaluated too ✨. And if the AS determines no additional interaction can occur (e.g., all the interaction methods have timed out, or a revocation request (Section 5.4) comes in from the client instance), the grant request can be moved to the finalized state 🥀.
approved 🎉: when an RO has approved a request and no further interaction with the end user is required, the grant request enters approved. In this state, responses to the client instance can include access tokens for API access (Section 3.2) and subject information (Section 3.4) 🎀. If continuation and updates are allowed for this grant request, the AS can include the continuation response (Section 3.1) 🧚. In this state, post-interaction continuation requests (Section 5.1) are not allowed and will result in an error, since all the interaction is assumed to be completed, so no more of that 🙅♀️. If the client instance sends a polling continuation request (Section 5.2) while the request is in this state, new access tokens (Section 3.2) can be issued in the response 🌈. Note that this always creates a new access token, but any existing access tokens could be rotated and revoked using the token management API (Section 6) 🔄. The client instance can send an update continuation request (Section 5.3) to modify the requested access, which makes the AS move the request back to the processing state for re-evaluation 💅. And if the AS determines that no additional tokens can be issued and no additional updates will be accepted (e.g., the continuation access tokens have expired), the grant is moved to the finalized state 🪦.
finalized 🔒: after the access tokens are issued, if the AS does not allow any more updates on the grant request, the grant request enters finalized. This state is also entered when an existing grant request is revoked by the client instance (Section 5.4), or otherwise revoked by the AS (such as through out-of-band action by the RO) 🚫. It can also be entered if the AS determines that no additional processing is possible, for example if the RO denied the requested access, or if interaction is required but no compatible interaction methods are available 😤. Once in this state, no new access tokens can be issued, no subject information can be returned, and no interactions can take place. Once in this state, the grant request is dead and cannot be revived 💀⚰️. If the client instance still wants future access, a new grant request can be created, which is totally unrelated to this one 🌸.
While it's possible to deploy an AS in a stateless environment, GNAP is a stateful protocol 🧠. Those deployments will need a way to manage the current state of the grant request in a secure and deterministic fashion, without relying on other components (like the client software) to keep track of the current state 🔐✨.
1.6. Sequences 🧚
GNAP can be used in a variety of ways to let the core delegation process happen 🦋. Lots of parts of this process are conditionally present depending on the context of the deployment, so not every step in this overview will happen in every situation 🌈.
Heads up: a connection between roles in this process does not automatically mean a specific protocol message is sent across the wire between the components filling those roles, or that a particular step is required every single time 🙈. For example, a client instance that only wants subject information directly, and doesn't call an RS, can skip all the steps involving the RS below. Totally valid 💅.
In some circumstances, the info needed at a given stage is communicated out of band or is pre-configured between the components or entities doing the roles 📬. For example, one entity can fill multiple roles, so explicit communication between those roles isn't necessary within the protocol flow 🫶. Also, some components might not be involved in every use case. For example, a client instance could be calling the AS just to get direct user information, and have no need to get an access token to call an RS 💖.
1.6.1. Overall Protocol Sequence ✨
okay so this diagram is basically the full GNAP story in one picture 🎀 it has a bunch of optional phases and connections, so don't panic if it looks like a lot. the later sections have zoomed-in diagrams that use the same conventions, so this one is your cheat sheet 🧚
.----------. .----------.
| End user | ~ ~ ~ ~ | Resource |
| | | Owner (RO) |
`----+-----` `-----+----`
║ ║
║ ║
(A) (B)
║ ║
║ ║
+-----+--+ ║ +------------+
| Client | (1) ║ | Resource |
|Instance| ║ | Server |
| | +-----------+---+ | (RS) |
| +--(2)-->| Authorization | | |
| |<-(3)---+ Server | | |
| | | (AS) | | |
| +--(4)-->| | | |
| |<-(5)---+ | | |
| | | | | |
| +---------------(6)------------->| |
| | | | (7) | |
| |<--------------(8)------------->| |
| | | | | |
| +--(9)-->| | | |
| |<-(10)--+ | | |
| | | | | |
| +---------------(11)------------>| |
| | | | (12) | |
| +--(13)->| | | |
| | | | | |
+--------+ +---------------+ +------------+
Legend:
===== indicates a possible interaction with a human
----- indicates an interaction between protocol roles
~ ~ ~ indicates a potential equivalence or out-of-band
communication between roles
Figure 3: Overall Sequence of GNAP
okay so here's the play-by-play, bestie 💅
- (A) the end user tells the client instance that they need resources on behalf of the RO 🫶 this could name the RS the client needs to call, the resources it needs, or the RO whose approval is needed. the RO and end user are usually the same person in real life, but GNAP doesn't just assume that they always are 🙈
- (1) the client instance figures out what access it needs and which AS to go to 🗺️ most of the time the client is already preconfigured with its AS and the kinds of access it wants, but some more dynamic setups are covered in Section 9.1 ✨
- (2) the client instance asks the AS for access (Section 2) 🙋♀️
- (3) the AS looks at the request and works out what it needs to fulfill it (see Section 4), then sends its response back to the client instance (Section 3) 📬
- (B) if interaction is needed, the AS talks to the RO (Section 4) to get authorization 👑 the interactive part can work in lots of ways, like web page redirects, apps, challenge/response protocols, or other methods 🌈 the RO approves the request for the client instance that the end user is operating, and a lot of GNAP's interaction methods let the client help the end user reach the AS so they can act as the RO 🫶
- (4) the client instance continues the grant at the AS (Section 5) ⭐ this might happen after it gets a signal that interaction is done (Section 4.2), or through periodic polling, depending on what the client software can do and which options were active in the grant request 🔥
- (5) if the AS decides access can be granted, it sends back a response to the client instance (Section 3) with an access token (Section 3.2) for calling the RS, plus any info about the RO that gets returned directly (Section 3.4) 🎀
- (6) the client instance uses the access token (Section 7.2) to call the RS 🦋
- (7) the RS checks whether the token is enough for the request by looking at the token 👀 how the RS does that is out of scope for this spec, but [GNAP-RS] has some options if you're curious 🧐
- (8) the client instance keeps calling the RS (Section 7.2) with the access token until either the RS or the client decides the token isn't valid anymore 🌸
- (9) when the token stops working, the client instance rotates the access token (Section 6.1) 🔄
- (10) the AS gives the client instance a new access token (Section 3.2) with the same rights as the original token returned in (5) 💜
- (11) the client instance uses the new access token (Section 7.2) to call the RS 🧚
- (12) the RS checks whether the new token is enough for the request, same as in (7) 💅
- (13) the client instance gets rid of the token (Section 6.2) once it's done accessing the RS and doesn't need it anymore 🧹
the following sections and Appendix B have more specific guidance on using GNAP in different situations and deployments 📚 and fun fact, the client instance doesn't have to request an access token or call an RS at all, and there can be no end user involved in the delegation either 🙌 GNAP is so flexible it's giving main character energy 👑✨
1.6.2. Redirect-Based Interaction ✨
Okay so, in this example 🌸 the client instance is a web app that wants access to resources on behalf of the current user, who is both the end user and the RO 💅. Since the client instance can send the user to any arbitrary URI and get responses back from the user's browser, the interaction happens through front-channel redirects using that browser 🦋. The redirection URI used for interaction is a service hosted by the AS in this example. The client instance keeps a persistent session with the user so the same person who starts the interaction is the one who comes back from it 🫶.
+--------+ +--------+ .----.
| Client | | AS | | End |
|Instance| | | | User |
| |<=(1)== Start Session ===============================+ |
| | | | | |
| +--(2)--- Request Access --------->| | | |
| | | | | |
| |<-(3)-- Interaction Needed -------+ | | |
| | | | | |
| +==(4)== Redirect for Interaction ===================>| |
| | | | +------+
| | | |<==(5)==>| |
| | | | AuthN | RO |
| | | | | |
| | | |<==(6)==>| |
| | | | AuthZ +------+
| | | | | End |
| |<=(7)== Redirect for Continuation ===================+ User |
| | | | `----`
| +--(8)--- Continue Request ------->| |
| | | |
| |<-(9)----- Grant Access ----------+ |
| | | |
| | | | +--------+
| +--(10)-- Access API ---------------------------->| RS |
| | | | | |
| |<-(11)-- API Response ---------------------------| |
| | | | +--------+
+--------+ +--------+
Figure 4: Diagram of a Redirect-Based Interaction 🎀
- (1) The client instance sets up a session with the user, playing the role of the end user 💖.
- (2) The client instance asks for access to the resource (Section 2). It says it can redirect to an arbitrary URI (Section 2.5.1.1) and receive a redirect from the browser (Section 2.5.2.1). It stores verification information for its redirect in the session created in (1) 🔐.
- (3) The AS figures out that interaction is needed and replies (Section 3) with a URI to send the user to (Section 3.3.1) and the info needed to verify the redirect (Section 3.3.5) in (7). It also includes the info the client instance will need to continue the request (Section 3.1) in (8). The AS links this continuation info to an ongoing request, which gets referenced in (4), (6), and (8) 🧩.
- (4) The client instance stores the verification and continuation info from (3) in the session from (1). Then it redirects the user to the URI (Section 4.1.1) given by the AS in (3). The user's browser loads the interaction redirect URI, and the AS loads the pending request based on the incoming URI generated in (3) 🚀.
- (5) The user authenticates at the AS, taking on the role of the RO 🔑.
- (6) As the RO, the user authorizes the pending request from the client instance ✅.
- (7) When the AS is done interacting with the user, it redirects the user back (Section 4.2.1) to the client instance using the redirect URI provided in (2). That redirect URI gets an interaction reference the AS associates with the ongoing request created in (2) and referenced in (4). It also gets a hash of the security information provided in (2) and (3). The client instance loads the verification information from (2) and (3) from the session created in (1), then calculates a hash (Section 4.2.3) based on it and continues only if the hash validates. Note that the client instance needs to make sure the parameters for the incoming request match what it's expecting from the session created in (1). It also needs to be ready for the end user never being sent back to the client instance, and handle timeouts appropriately 😭.
- (8) The client instance loads the continuation info from (3) and sends the interaction reference from (7) in a request to continue the request (Section 5.1). The AS validates the interaction reference, making sure it's associated with the request being continued 🙌.
- (9) If the request has been authorized, the AS grants access to the information in the form of access tokens (Section 3.2) and direct subject information (Section 3.4) to the client instance 👑.
- (10) The client instance uses the access token (Section 7.2) to call the RS 📞.
- (11) The RS validates the access token and returns an appropriate response for the API 🦋.
Want to see the actual protocol messages for this method? They're in Appendix B.1 🧚.
1.6.3. User Code Interaction ✨
okay so this flow is for a client instance that's a device with no real keyboard or browser situation going on 📟. It can show a short, human-readable code to the user and tell them where to type it in, which is a known URI. The user enters that code at an interactive service hosted by the AS in this example. The client instance can't show the user an arbitrary URI, and it also can't accept incoming HTTP requests from the user's browser 🙈. While it's waiting for the RO to approve the request, the client instance keeps polling the AS 🔁. The user does their part on a secondary device, and in this example the user is both the end user and the RO 💜. The user isn't assumed to use the same web browser for the AS interaction that they'd use with the client instance, so the diagram doesn't show the user connected to the client instance. no cap, that's on purpose 💅
+--------+ +--------+ .----.
| Client | | AS | | End |
|Instance+--(1)--- Request Access --------->| | | User |
| | | | | |
| |<-(2)-- Interaction Needed -------+ | | |
| | | | | |
| +==(3)==== Display User Code ========================>| |
| | | | | |
| | | |<==(4)===+ |
| | | |Open URI | |
| | | | +------+
| | | |<==(5)==>| RO |
| | | | AuthN | |
| +--(9)--- Continue Request (A) --->| | | |
| | | |<==(6)==>| |
| |<-(10)-- Not Yet Granted (Wait) --+ | Code | |
| | | | | |
| | | |<==(7)==>| |
| | | | AuthZ | |
| | | | | |
| | | |<==(8)==>| |
| | | |Complete | |
| | | | +------+
| +--(11)-- Continue Request (B) --->| | | End |
| | | | | User |
| |<-(12)----- Grant Access ---------+ | `----`
| | | |
| | | | +--------+
| +--(13)-- Access API ---------------------------->| RS |
| | | | | |
| |<-(14)-- API Response ---------------------------+ |
| | | | +--------+
+--------+ +--------+
Figure 5: Diagram of a User-Code-Based Interaction
- (1) 🚀 The client instance requests access to the resource (Section 2). The client instance indicates that it can display a user code (Section 2.5.1.3).
- (2) 🤔 The AS figures out that interaction is needed and responds (Section 3) with a user code to communicate to the user (Section 3.3.3). The AS also includes the info the client instance needs to continue the request (Section 3.1) in (8) and (10). The AS ties this continuation info to an ongoing request that gets referenced in (4), (6), (8), and (10). main character energy 🎀
- (3) 📥 The client instance stores the continuation info from (2) so it can use it in (8) and (10). Then it shows the user the code from the AS in (2) (Section 4.1.2). 🌸
- (4) 🌐 The user takes their browser to the user code URI. This URI is stable, and it can be shared through the client software's documentation, the AS documentation, or the client software itself. Since the RO is assumed to use a secondary device with the AS, the client instance doesn't have a way to launch the RO's browser at this URI 🙅♀️.
- (5) 🔐 The end user logs in at the AS and becomes the RO. iykyk 🦋
- (6) ⌨️ The RO types in the code from (3) on the AS. The AS checks this code against a current request that's still in process ✨.
- (7) ✅ As the RO, the user approves the pending request from the client instance. slay 💅
- (8) 🎉 Once the AS is done with the user, it tells the RO that the request is complete.
- (9) 🔄 Meanwhile, the client instance loads the continuation info it stored at (3) and keeps going with the request (Section 5). The AS figures out which ongoing access request this is and checks its state 🧐.
- (10) ⏳ If the RO hasn't approved the access request yet in (6), the AS tells the client instance to keep going with the request (Section 3.1) later on, through more polled continuation requests. This response can include updated continuation info, plus info about how long the client instance should wait before calling again 🕰️. The client instance swaps out the continuation info it stored from the previous response (2). Also, the AS might need to decide that the RO took too long to approve the request, and then send an appropriate error back to the client instance 😬.
- (11) 🔁 The client instance keeps polling the AS (Section 5.2) using the new continuation info from (9). ngl this is the most patient part of the whole flow 🫶
- (12) 🎁 If the RO has approved the request, the AS grants access to the info in the form of access tokens (Section 3.2) and direct subject info (Section 3.4) to the client instance. ICONIC 👑
- (13) 🔑 The client instance uses the access token (Section 7.2) to call the RS.
- (14) 📦 The RS validates the access token and sends back an appropriate response for the API. 🌈
An example set of protocol messages for this method can be found in Appendix B.2. 📜🧚
1.6.4. Asynchronous Authorization ✨
okay so in this one the end user and the RO are two different people 👀, and the RO never talks to the client instance directly 🫶. instead the AS reaches out to the RO asynchronously while the request is being processed, to get the RO's authorization for the client instance's request. meanwhile the client instance keeps checking back with the AS while it waits for the RO to say yes 💅
+--------+ +--------+ .----.
| Client | | AS | | RO |
|Instance+--(1)--- Request Access --------->| | | |
| | | | | |
| |<-(2)-- Not Yet Granted (Wait) ---+ | | |
| | | |<==(3)==>| |
| | | | AuthN | |
| +--(6)--- Continue Request (A) --->| | | |
| | | |<==(4)==>| |
| |<-(7)-- Not Yet Granted (Wait) ---+ | AuthZ | |
| | | | | |
| | | |<==(5)==>| |
| | | |Completed| |
| | | | | |
| +--(8)--- Continue Request (B) --->| | `----`
| | | |
| |<-(9)------ Grant Access ---------+ |
| | | |
| | | | +--------+
| +--(10)-- Access API ---------------------------->| RS |
| | | | | |
| |<-(11)-- API Response ---------------------------+ |
| | | | +--------+
+--------+ +--------+
Figure 6: Diagram of an Asynchronous Authorization Process, with
No End-User Interaction
- (1) the client instance requests access to the resource (Section 2). it doesn't send any interaction modes to the server, which tells the AS it doesn't expect to interact with the RO 🙅♀️. it can also say which RO it needs authorization from, if it knows, by using the subject request field (Section 2.2) and user request field (Section 2.4). and it's also possible for the AS to figure out which RO needs to be contacted just from what access is being asked for 🧠
- (2) the AS decides interaction is needed, but the client instance can't interact with the RO. so the AS responds (Section 3) with the info the client instance needs to continue the request (Section 3.1) in (6) and (8), plus a signal that it should wait before checking on the request again ⏳. the AS ties this continuation info to an ongoing request that gets referenced in (3), (4), (5), (6), and (8) 🎀
- (3) the AS works out which RO to contact based on the request in (1), using some mix of the user request (Section 2.4), the subject request (Section 2.2), the access request (Section 2.1), and other policy info 🗺️. then it contacts the RO and authenticates them 🔐
- (4) the RO authorizes the pending request from the client instance 💖
- (5) once the AS is done talking to the RO, it tells the RO that the request is completed ✅
- (6) meanwhile, the client instance loads the continuation info stored at (2) and continues the request (Section 5). the AS figures out which ongoing access request this is and checks its state 👀
- (7) if the RO hasn't authorized the access request yet (the thing from (6)), the AS tells the client instance to continue the request (Section 3.1) later through more polling 🔁. this is not an error message, bestie, since nothing has gone wrong yet 🙅♀️. this response can include refreshed credentials and info on how long the client instance should wait before calling again ⏱️. the client instance swaps out the continuation info it stored from the previous response (2) for the new stuff. also, the AS may need to decide the RO has taken too long to approve, and send back an appropriate error to the client instance 😬
- (8) the client instance keeps polling the AS (Section 5.2) using the new continuation info from (7) 🔄
- (9) if the request has been authorized, the AS hands out access to the information in the form of access tokens (Section 3.2) and direct subject information (Section 3.4) to the client instance 🎉
- (10) the client instance uses the access token (Section 7.2) to call the RS 🚀
- (11) the RS validates the access token and sends back an appropriate response for the API 💅
an example set of protocol messages for this method can be found in Appendix B.4 📄
additional considerations for asynchronous interactions like this are discussed in Section 11.36 🧠✨
1.6.5. Software-Only Authorization ✨
in this flow the AS policy lets the client instance make a call on its own behalf 🤖, so no RO has to show up at runtime to approve the decision. there's no explicit RO, so the client instance doesn't interact with one at all 🙈
+--------+ +--------+
| Client | | AS |
|Instance+--(1)--- Request Access --->| |
| | | |
| |<-(2)---- Grant Access -----+ |
| | | | +--------+
| +--(3)--- Access API ------------------->| RS |
| | | | | |
| |<-(4)--- API Response ------------------+ |
| | | | +--------+
+--------+ +--------+
Figure 7: Diagram of a Software-Only Authorization, with No End
User or Explicit Resource Owner
- (1) the client instance requests access to the resource (Section 2). it doesn't send any interaction modes to the server 🙅♀️
- (2) the AS decides the request is authorized based on the identity of the client instance making the request and the access requested (Section 2.1) 🪪. the AS grants access to the resource in the form of access tokens (Section 3.2) to the client instance 🎟️. note that direct subject information (Section 3.4) isn't really applicable here, since there's no user involved 🫥
- (3) the client instance uses the access token (Section 7.2) to call the RS 📞
- (4) the RS validates the access token and sends back an appropriate response for the API ✅
an example set of protocol messages for this method can be found in Appendix B.3 📄
1.6.6. Refreshing an Expired Access Token ✨
in this one the client instance gets an access token to reach an RS through some valid GNAP process 🔑. it uses that token at the RS for a while, but eventually the token expires 💀. so the client instance gets a fresh access token by rotating the expired one's value at the AS, using the token management API 🔄✨
+--------+ +--------+
| Client | | AS |
|Instance+--(1)--- Request Access ----------------->| |
| | | |
| |<-(2)--- Grant Access --------------------+ |
| | | |
| | +--------+ | |
| +--(3)--- Access Resource --->| RS | | |
| | | | | |
| |<-(4)--- Success Response ---+ | | |
| | | | | |
| | ( Time Passes ) | | | |
| | | | | |
| +--(5)--- Access Resource --->| | | |
| | | | | |
| |<-(6)--- Error Response -----+ | | |
| | +--------+ | |
| | | |
| +--(7)--- Rotate Token ------------------->| |
| | | |
| |<-(8)--- Rotated Token -------------------+ |
| | | |
+--------+ +--------+
Figure 8: Diagram of the Process of Refreshing an Expired Access
Token
- (1) the client instance requests access to the resource (Section 2) 🙋♀️
- (2) the AS grants access to the resource (Section 3) with an access token (Section 3.2) that works at the RS. the access token response includes a token management URI 🔗
- (3) the client instance uses the access token (Section 7.2) to call the RS 📞
- (4) the RS validates the access token and sends back an appropriate response for the API ✅
- (5) time passes, and then the client instance uses the access token to call the RS again ⏰
- (6) the RS validates the access token and sees that it's expired 😭. the RS responds to the client instance with an error 💔
- (7) the client instance calls the token management URI returned in (2) to rotate the access token (Section 6.1). it uses the access token (Section 7.2) in this call too, along with the appropriate key; see Section 6.1 for details 🔐
- (8) the AS validates the rotation request, including the signature and keys presented in (7), and refreshes the access token (Section 3.2.1) 🫶. the response includes a new version of the access token and can also include updated token management info, which the client instance stores in place of the values returned in (2) 🌸
1.6.7. Requesting Subject Information Only
okay so in this one the client instance never calls an RS and never asks for an access token 🎀 it just wants the direct subject information (Section 3.4) bestie 💅 and there are lots of different interaction modes that could work here, so the diagram only shows them in the abstract, as functions of the AS 🦋
+--------+ +--------+ .----.
| Client | | AS | | End |
|Instance| | | | User |
| +--(1)--- Request Access --------->| | | |
| | | | | |
| |<-(2)-- Interaction Needed -------+ | | |
| | | | | |
| +==(3)== Facilitate Interaction =====================>| |
| | | | +------+
| | | |<==(4)==>| RO |
| | | | AuthN | |
| | | | | |
| | | |<==(5)==>| |
| | | | AuthZ +------+
| | | | | End |
| |<=(6)== Signal Continuation =========================+ User |
| | | | `----`
| +--(7)--- Continue Request ------->| |
| | | |
| |<-(8)----- Grant Access ----------+ |
| | | |
+--------+ +--------+
Figure 9: Diagram of the Process of Requesting and Releasing Subject
Information apart from Access Tokens
* (1) the client instance asks for access to subject information (Section 2) 🌸
* (2) the AS figures out that interaction is needed and replies (Section 3) with the info the client needs to get the user interacting (Section 3.3) ✨
* (3) the client instance gets the user interacting with the AS (Section 4) like the AS asked in (2) 🫶
* (4) the user logs in at the AS, and they're playing the role of the RO here 🔐
* (5) as the RO, the user approves the pending request from the client instance 💖
* (6) once the AS is done with the user, it sends them back to the client instance and signals that things can continue 🦋
* (7) the client instance loads the continuation info from (2) and calls the AS to keep going (Section 5) 🧚
* (8) if the request got approved, the AS hands over the requested direct subject information (Section 3.4) to the client instance 👑 at this point the user is generally considered "logged in" to the client instance, based on the identifiers and assertions the AS gave it 🎀 and ngl, the AS can restrict what subject information comes back, so it might not match what the client asked for. check Section 3.4 for the deets 💀
1.6.8. Cross-User Authentication
okay this one is literally so good 😭 the end user and the RO are two different people 🫶 the client instance already knows who the end user is, probably from a separate authentication process 🔑 but the end user, who's operating the client instance, needs subject info about another person in the system, the RO 🌈 the RO gets a chance to release that info through an asynchronous interaction with the AS ⏳ this would happen, for example, when the end user is a call-center agent and the RO is a customer letting the agent access their account on their behalf 📞💜
.----. .----.
| End | | RO |
| User |<=================(1)== Identify RO ==================>| |
| | | |
| | +--------+ +--------+ | |
| +==(2)==>| Client | | AS | | |
| | RO ID |Instance| | | | |
| | | | | | | |
| | | +--(3)-- Req. ---->| | | |
| | | | | | | |
| | | |<-(4)-- Res. -----+ | | |
| | | | | |<==(5)==>| |
| | | | | | AuthN | |
| | | | | | | |
| | | | | |<==(6)==>| |
| | | | | | AuthZ | |
| | | | | | | |
| | | | | |<==(7)==>| |
| | | |<-(8)--- Finish --+ |Completed| |
| | | | | | | |
| | | +--(9)--- Cont. -->| | | |
| | | | | | | |
| | | |<-(10)-- Subj. ---+ | | |
| |<=(11)==+ | Info | | | |
| | Return | | | | | |
| | RO | | | | | |
| | Info | | | | | |
`----` +--------+ +--------+ `----`
Figure 10: Diagram of Cross-User Authorization, Where the End
User and RO Are Different
Precondition: the end user is authenticated to the client instance, and the client instance has an identifier for the end user that it can show the AS 🪪 this identifier should be unique to that particular session between the client instance and the AS 🔐 the client instance is also known to the AS and is allowed to use this advanced stuff, where info about someone other than the end user gets returned to the client instance 👑
* (1) the RO tells the end user a human-readable identifier, like an email address or account number 📧 this happens out of band from the protocol, like over the phone between the two of them 📞 and note that the RO is not interacting with the client instance 🙈
* (2) the end user gives that identifier to the client instance 💅 how the identifier gets to the client instance is out of scope for this spec 🤷♀️
* (3) the client instance asks for access to subject information (Section 2) 🦋 the request has the RO's identifier in the sub_ids field of the subject information request (Section 2.2), and the end user's identifier in the user field (Section 2.4) ✨ the request includes no interaction start methods, since the end user isn't expected to be the one interacting with the AS 🙅♀️ it does include the push-based interaction finish method (Section 2.5.2.2) so the AS can signal the client instance when the interaction with the RO has wrapped up 🎀
* (4) the AS sees that the identifiers for the end user and the subject being requested are different 👀 it figures out it can reach out to the RO asynchronously for approval 🧚 while it's doing that, it returns a continuation response (Section 3.1) with a finish nonce, so the client instance can pick the grant request back up after the RO interaction is done 🌸
* (5) the AS contacts the RO and has them authenticate to the system 🔑 how it does that is outside the scope of this spec, but the AS knows who the RO is from the Subject Identifier sent in (3) 🌈
* (6) the RO gets prompted to authorize the end user's request via the client instance 💌 since the end user was identified in (3) through the user field, the AS can show that info to the RO during the authorization request 👀
* (7) the RO finishes the authorization with the AS ✅ the AS marks the request as _approved_ 💖
* (8) the RO pushes the interaction finish message (Section 4.2.2) to the client instance 📨 and here's the thing: if the RO can't be reached or the RO denies the request, the AS still sends the interaction finish message to the client instance 😭 after that, the client instance can try to negotiate next steps if it's able to 🫶
* (9) the client instance checks the interaction finish message and continues the grant request (Section 5.1) 🔍
* (10) the AS returns the RO's subject information (Section 3.4) to the client instance 🎁
* (11) the client instance can display or otherwise use the RO's user info in its session with the end user 💁♀️ and since the client instance asked for different sets of user info in (3), it does not mix up the end user with the RO 🧠👑
Additional considerations for asynchronous interactions like this are discussed in Section 11.36 🦋✨
2. Requesting Access ✨
okay so to start a request, the client instance sends an HTTP POST with a JSON [RFC8259] document to the grant endpoint of the AS. the grant endpoint is a URI that uniquely identifies the AS to client instances and serves as the identifier for the AS 🔑. the document is a JSON object where each field represents a different aspect of the client instance's request, and each field gets its own subsection below 💅.
access_token (object / array of objects): describes the rights and properties associated with the requested access token. REQUIRED if requesting an access token. See Section 2.1.
subject (object): describes the information about the RO that the client instance wants returned directly in the response from the AS. REQUIRED if requesting subject information. See Section 2.2.
client (object / string): describes the client instance making this request, including the key the client instance will use to protect this request, any continuation requests at the AS, and any user-facing information about the client instance used in interactions. REQUIRED. See Section 2.3.
user (object / string): identifies the end user to the AS in a manner the AS can verify, either directly or by interacting with the end user to determine their status as the RO. OPTIONAL. See Section 2.4.
interact (object): describes the modes the client instance supports for allowing the RO to interact with the AS, and modes for the client instance to receive updates when interaction is complete. REQUIRED if interaction is supported. See Section 2.5.
extensions can add more members to this request object using the "GNAP Grant Request Parameters" registry (Section 10.3) 🧚.
here's a non-normative example of a grant request (vibes only, not a rule 🫶):
{
"access_token": {
"access": [
{
"type": "photo-api",
"actions": [
"read",
"write",
"dolphin"
],
"locations": [
"https://server.example.net/",
"https://resource.local/other"
],
"datatypes": [
"metadata",
"images"
]
},
"dolphin-metadata"
]
},
"client": {
"display": {
"name": "My Client Display Name",
"uri": "https://example.net/client"
},
"key": {
"proof": "httpsig",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "xyz-1",
"alg": "RS256",
"n": "kOB5rR4Jv0GMeL...."
}
}
},
"interact": {
"start": ["redirect"],
"finish": {
"method": "redirect",
"uri": "https://client.example.net/return/123455",
"nonce": "LKLTI25DK82FX4T4QFZC"
}
},
"subject": {
"sub_id_formats": ["iss_sub", "opaque"],
"assertion_formats": ["id_token"]
}
}
sending a request to the grant endpoint creates a grant request in the processing state 🌸. the AS processes it to figure out whether interaction or authorization is needed (which moves it to the pending state), or whether access can be granted right away (which moves it to the approved state) ✨.
The request MUST be sent as a JSON object in the content of the HTTP POST request with Content-Type application/json. A key proofing mechanism MAY define an alternative content type, as long as the content is formed from the JSON object. For example, the attached JSON Web Signature (JWS) key proofing mechanism (see Section 7.3.4) places the JSON object into the payload of a JWS wrapper, which is in turn sent as the message content 🦋.
2.1. Requesting Access to Resources
if the client instance is requesting one or more access tokens so it can access an API, the client instance MUST include an access_token field. This field MUST be an object (for a single access token, Section 2.1.1) or an array of these objects (for multiple access tokens, Section 2.1.2), as the following subsections explain 💜.
2.1.1. Requesting a Single Access Token
to request a single access token, the client instance sends an access_token object made up of these fields:
access (array of objects/strings): describes the rights the client instance is requesting for the access token to be used at the RS. REQUIRED. See Section 8.
label (string): a unique name chosen by the client instance to refer to the resulting access token. The AS treats the value of this field as opaque, and it isn't meant to be shown to or used by the end user 🙈. If this field is included in the request, the AS MUST include the same label in the token response (Section 3.2). REQUIRED if used as part of a request for multiple access tokens (Section 2.1.2); OPTIONAL otherwise.
flags (array of strings): a set of flags that indicate the desired attributes or behavior the AS should attach to the access token. OPTIONAL.
the values of the flags field defined by this specification are as follows:
"bearer": if this flag is included, the access token being requested is a bearer token. If this flag is omitted, the access token is bound to the key the client instance used in this request (or that key's most recent rotation), and the access token MUST be presented using the same key and proofing method. Methods for presenting bound and bearer access tokens are described in Section 7.2. See Section 11.9 for additional considerations on the use of bearer tokens 🔐.
Flag values MUST NOT be included more than once. If the request includes a flag value multiple times, the AS MUST return an invalid_flag error defined in Section 3.6. not the vibe we want 💀
additional flags can be defined by extensions using the "GNAP Access Token Flags" registry (Section 10.4) 🌈.
in this non-normative example, the client instance is requesting access to a complex resource described by a pair of access request objects 🎀:
"access_token": {
"access": [
{
"type": "photo-api",
"actions": [
"read",
"write",
"delete"
],
"locations": [
"https://server.example.net/",
"https://resource.local/other"
],
"datatypes": [
"metadata",
"images"
]
},
{
"type": "walrus-access",
"actions": [
"foo",
"bar"
],
"locations": [
"https://resource.other/"
],
"datatypes": [
"data",
"pictures",
"walrus whiskers"
]
}
],
"label": "token1-23"
}
if access is approved, the resulting access token is valid for the described resource ✨. since the bearer flag isn't provided in this example, the token is bound to the client instance's key (or its most recent rotation) 🔒. the token is labeled "token1-23", and the token response structure is described in Section 3.2.1 🎀.
2.1.2. Requesting Multiple Access Tokens
to request that multiple access tokens come back in a single response, the client instance sends an array of objects as the value of the access_token parameter. Each object MUST conform to the request format for a single access token request, as specified in Section 2.1.1. Additionally, each object in the array MUST include the label field, and all values of these fields MUST be unique within the request. If the client instance doesn't include a label value for any entry in the array, or the values of the label field aren't unique within the array, the AS MUST return an "invalid_request" error (Section 3.6) 🚫.
the following non-normative example shows a request for two separate access tokens, token1 and token2 🍓:
"access_token": [
{
"label": "token1",
"access": [
{
"type": "photo-api",
"actions": [
"read",
"write",
"dolphin"
],
"locations": [
"https://server.example.net/",
"https://resource.local/other"
],
"datatypes": [
"metadata",
"images"
]
},
"dolphin-metadata"
]
},
{
"label": "token2",
"access": [
{
"type": "walrus-access",
"actions": [
"foo",
"bar"
],
"locations": [
"https://resource.other/"
],
"datatypes": [
"data",
"pictures",
"walrus whiskers"
]
}
],
"flags": [ "bearer" ]
}
]
all approved access requests come back in the response structure for multiple access tokens (Section 3.2.2), using the values of the label fields from the request 👑.
2.2. Requesting Subject Information
okay so, if the client instance wants info about the RO from the AS, it sends a subject field as a JSON object 🎀 This object MAY contain the following fields, bestie ✨
sub_id_formats (array of strings): An array of Subject Identifier subject formats requested for the RO, as defined by [RFC9493]. REQUIRED if Subject Identifiers are requested. 💖
assertion_formats (array of strings): An array of requested assertion formats. Possible values include id_token for an OpenID Connect ID Token [OIDC] and saml2 for a Security Assertion Markup Language (SAML) 2 assertion [SAML2]. Additional assertion formats can be defined in the "GNAP Assertion Formats" registry (Section 10.6). REQUIRED if assertions are requested. 🦋
sub_ids (array of objects): An array of Subject Identifiers representing the subject for which information is being requested. Each object is a Subject Identifier as defined by [RFC9493]. All identifiers in the sub_ids array MUST identify the same subject. If omitted, the AS SHOULD assume that subject information requests are about the current user and SHOULD require direct interaction or proof of presence before releasing information. OPTIONAL. 🌸
Additional fields can be defined in the "GNAP Subject Information Request Fields" registry (Section 10.5). 🧚
"subject": {
"sub_id_formats": [ "iss_sub", "opaque" ],
"assertion_formats": [ "id_token", "saml2" ]
}
The AS can determine the RO's identity and permission for releasing this information through interaction with the RO (Section 4), AS policies, or assertions presented by the client instance (Section 2.4). If this is determined positively, the AS MAY return the RO's information in its response (Section 3.4) as requested. 👑
Subject Identifier types requested by the client instance serve only to identify the RO in the context of the AS and can't be used as communication channels by the client instance, as discussed in Section 3.4. 🙈
2.3. Identifying the Client Instance
When sending a new grant request to the AS, the client instance MUST identify itself by including its client information in the client field of the request and by signing the request with its unique key as described in Section 7.3. Note that once a grant has been created and is in either the _pending_ or the _approved_ state, the AS can determine which client is associated with the grant by dereferencing the continuation access token sent in the continuation request (Section 5). As a consequence, the client field is not sent or accepted for continuation requests. 💅
Client information is sent by value as an object or by reference as a string (see Section 2.3.1). 🌈
When client instance information is sent by value, the client field of the request consists of a JSON object with the following fields. ✨
key (object / string): The public key of the client instance to be used in this request as described in Section 7.1 or a reference to a key as described in Section 7.1.1. REQUIRED. 🔥
class_id (string): An identifier string that the AS can use to identify the client software comprising this client instance. The contents and format of this field are up to the AS. OPTIONAL. 🍓
display (object): An object containing additional information that the AS MAY display to the RO during interaction, authorization, and management. OPTIONAL. See Section 2.3.2. 💜
"client": {
"key": {
"proof": "httpsig",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "xyz-1",
"alg": "RS256",
"n": "kOB5rR4Jv0GMeLaY6_It_r3ORwdf8ci_JtffXyaSx8..."
}
},
"class_id": "web-server-1234",
"display": {
"name": "My Client Display Name",
"uri": "https://example.net/client"
}
}
Additional fields can be defined in the "GNAP Client Instance Fields" registry (Section 10.7). 🎀
Absent additional attestations, profiles, or trust mechanisms, both the display and class_id fields are self-declarative, presented by the client instance. The AS needs to exercise caution in their interpretation, taking them as a hint but not as absolute truth. 🙈 The class_id field can be used in a variety of ways to help the AS make sense of the particular context in which the client instance is operating. In corporate environments, for example, different levels of trust might apply depending on security policies. This field aims to help the AS adjust its own access decisions for different classes of client software. It is possible to configure a set of values and rules during a pre-registration and then have the client instances provide them later in runtime as a hint to the AS. In other cases, the client runs with a specific AS in mind, so a single hardcoded value would be acceptable (for instance, a set-top box with a class_id claiming to be "FooBarTV version 4"). While the client instance may not have contacted the AS yet, the value of this class_id field can be evaluated by the AS according to a broader context of dynamic use, alongside other related information available elsewhere (for instance, corresponding fields in a certificate). If the AS is not able to interpret or validate the class_id field, it MUST either return an invalid_client error (Section 3.6) or interpret the request as if the class_id were not present. See additional discussion of client instance impersonation in Section 11.15. 🦋
The client instance MUST prove possession of any presented key by the proofing mechanism associated with the key in the request. Key proofing methods are defined in the "GNAP Key Proofing Methods" registry (Section 10.16), and an initial set of methods is described in Section 7.3. 🔐
If the same public key is sent by value on different access requests, the AS MUST treat these requests as coming from the same client instance for purposes of identification, authentication, and policy application. 💞
If the AS does not know the client instance's public key ahead of time, the AS can choose how to process the unknown key. Common approaches include:
- Allowing the request and requiring RO authorization in a trust-on-first-use model
- Limiting the client's requested access to only certain APIs and information
- Denying the request entirely by returning an invalid_client error (Section 3.6)
The client instance MUST NOT send a symmetric key by value in the key field of the request, as doing so would expose the key directly instead of simply proving possession of it. See considerations on symmetric keys in Section 11.7. To use symmetric keys, the client instance can send the key by reference (Section 7.1.1) or send the entire client identity by reference (Section 2.3.1). 🫶
The client instance's key can be pre-registered with the AS ahead of time and associated with a set of policies and allowable actions pertaining to that client. If this pre-registration includes other fields that can occur in the client request object described in this section, such as class_id or display, the pre-registered values MUST take precedence over any values given at runtime. Additional fields sent during a request but not present in a pre-registered client instance record at the AS SHOULD NOT be added to the client's pre-registered record. See additional considerations regarding client instance impersonation in Section 11.15. 👑
A client instance that is capable of talking to multiple ASes SHOULD use a different key for each AS to prevent a class of mix-up attacks as described in Section 11.31, unless other mechanisms can be used to assure the identity of the AS for a given request. 😭
2.3.1. Identifying the Client Instance by Reference
If the client instance has an instance identifier that the AS can use to determine appropriate key information, the client instance can send this instance identifier as a direct reference value in lieu of the client object. The instance identifier MAY be assigned to a client instance at runtime through a grant response (Section 3.5) or MAY be obtained in another fashion, such as a static registration process at the AS. 🌸
"client": "client-541-ab"
When the AS receives a request with an instance identifier, the AS MUST ensure that the key used to sign the request (Section 7.3) is associated with the instance identifier. 💜
If the AS does not recognize the instance identifier, the request MUST be rejected with an invalid_client error (Section 3.6). 💀
2.3.2. Providing Displayable Client Instance Information
If the client instance has additional information to display to the RO during any interactions at the AS, it MAY send that information in the "display" field. This field is a JSON object that declares information to present to the RO during any interactive sequences. 🍓
name (string): Display name of the client software. RECOMMENDED. ✨
uri (string): User-facing information about the client software, such as a web page. This URI MUST be an absolute URI. OPTIONAL. 🌐
logo_uri (string): Display image to represent the client software. This URI MUST be an absolute URI. The logo MAY be passed by value by using a data: URI [RFC2397] referencing an image media type. OPTIONAL. 🎨
"display": {
"name": "My Client Display Name",
"uri": "https://example.net/client",
"logo_uri": "data:image/png;base64,Eeww...="
}
Additional display fields can be defined in the "GNAP Client Instance Display Fields" registry (Section 10.8). 🧚
The AS SHOULD use these values during interaction with the RO. The values are for informational purposes only and MUST NOT be taken as authentic proof of the client instance's identity or source. The AS MAY restrict display values to specific client instances, as identified by their keys in Section 2.3. See additional considerations for displayed client information in Section 11.15 and for the logo_uri in particular in Section 11.16. 🌈
2.3.3. Authenticating the Client Instance ✨
okay so if the key the client shows is one the AS already knows, and it belongs to exactly one instance of the client software, then just presenting the key and proving you hold it is enough to authenticate that client instance to the AS 🔐. The AS MAY attach policies to that client instance through this key, like limiting which resources it can ask for and which interaction methods it can use 💅. For example, only specific client instances with certain known keys might get access tokens without the AS talking directly to the RO, kinda like in Appendix B.3 🦋.
Presenting a key also lets the AS strongly link multiple requests coming from the same client instance 🧚. That works when the AS knows the key ahead of time and can use it to authenticate the client instance, but it's also true if the key is ephemeral and was made just for this batch of requests 🫶. So the AS MAY allow client instances to make requests with unknown keys too 👑. This is the move for ephemeral client instances (like single-page apps) and for client software with tons of long-lived instances (like mobile apps). They can generate key pairs per instance and use them in the protocol without a separate registration step, which is so efficient, bestie 🌸. The AS MAY limit which capabilities are made available to client instances with unknown keys 🔥. For example, the AS could have a policy that says only previously registered client instances can request particular resources, or that every client instance with an unknown key has to be interactively approved by an RO 💀.
2.4. Identifying the User 💖
If the client instance knows who the end user is through one or more identifiers or assertions, it MAY send that info to the AS in the user field 🎀. The client instance MAY pass this information by value or by reference (see Section 2.4.1) 🌈.
sub_ids (array of objects): An array of Subject Identifiers for the end user, as defined by [RFC9493]. OPTIONAL.
assertions (array of objects): An array containing assertions as objects. Each one has the assertion format and the assertion value as the JSON string serialization of the assertion, as defined in Section 3.4. OPTIONAL.
"user": {
"sub_ids": [ {
"format": "opaque",
"id": "J2G8G8O4AZ"
} ],
"assertions": [ {
"format": "id_token",
"value": "eyj..."
} ]
}
Subject Identifiers are just hints for the AS when it's figuring out the RO 🕵️♀️. They MUST NOT be treated as authoritative statements that a particular RO is at the client instance and acting as the end user 🙅♀️.
Assertions from the client instance SHOULD be validated by the AS ✅. The details of that validation are outside this spec, but common steps include checking the assertion's signature against a trusted signing key, checking that the audience and issuer map to the values you'd expect, and checking the assertion's time window ⏰. Fair warning though: in lots of use cases some of those steps get relaxed 🙈. For example, an AS acting as an identity provider (IdP) could expect that the assertions being presented were issued by the AS to the client software. The AS would then verify that the AS is the issuer of the assertion (not the audience), and that the client instance is the audience instead 🧠. Similarly, an AS might accept a recently expired assertion to help bootstrap a new session with a specific end user 🍓.
If the identified end user doesn't match the RO present at the AS during an interaction step, and the AS isn't explicitly allowing a cross-user authorization, the AS SHOULD reject the request with an unknown_user error (Section 3.6) 🚫.
If the AS trusts the client instance to present verifiable assertions or known Subject Identifiers (like an opaque identifier the AS issued for this specific client instance), the AS MAY decide, based on its policy, to skip interaction with the RO. This is true even if the client instance includes one or more interaction modes in its request 🦋.
See Section 11.30 for considerations for the AS when it accepts and processes assertions from the client instance 📚.
2.4.1. Identifying the User by Reference 🎀
The AS can identify the current end user to the client instance with a reference. The client instance can use that reference to refer to the end user across multiple requests 💜. If the client instance has a reference for the end user at this AS, it MAY pass that reference as a string. The format of this string is opaque to the client instance 🫧.
"user": "XUT2MFM1XBIKJKSDU8QM"
One way to dynamically get a user reference is when the AS returns an opaque Subject Identifier, as described in Section 3.4 🪄. Other ways of configuring a client instance with a user identifier are out of scope for this spec 🙃. The AS decides how long these user references live and whether they're valid, and that lifetime isn't exposed to the client instance in GNAP ⏳. So a client instance using a user reference will probably keep using it until it stops working 😭.
User reference identifiers aren't meant to be human-readable user identifiers or structured assertions 🙅♀️. If the client wants to send either of those, it can use the full user request object (Section 2.4) instead 💅.
If the AS doesn't recognize the user reference, it MUST return an unknown_user error (Section 3.6) 💀.
2.5. Interacting with the User 🌸
Often, the AS will need to interact with the RO (Section 4) to approve a requested delegation to the client instance, for both access to resources and direct subject information 👑. A lot of the time, the end user of the client instance is the same person as the RO, so the client instance can drive the interaction directly by facilitating it, like redirecting to a URI or launching an application 🚀. Other times, the client instance can pass along info to start the RO's interaction on a secondary device, or it waits for the RO to approve the request asynchronously ⏳. The client instance could also be told that the interaction has wrapped up through a callback mechanism 🔔.
The client instance declares the parameters for the interaction methods it can support using the interact field 🧁.
The interact field is a JSON object with three keys. Their values declare how the client can start and complete the request, and also give the AS hints about user preferences like locale 🌍. A client instance MUST NOT declare an interaction mode it doesn't support 🚫. The client instance MAY send multiple modes in the same request. There's no preference order specified in this request, so no vibes-based ranking 🙅♀️. An AS MAY respond to any, all, or none of the presented interaction modes (Section 3.3) in a request, depending on its capabilities and what's allowed to fulfill the request 🤷♀️.
start (array of objects/strings): Indicates how the client instance can start an interaction. REQUIRED. See Section 2.5.1.
finish (object): Indicates how the client instance can receive an indication that interaction has finished at the AS. OPTIONAL. See Section 2.5.2.
hints (object): Provides additional information to inform the interaction process at the AS. OPTIONAL. See Section 2.5.3.
In the following non-normative example, the client instance says it can redirect (Section 2.5.1.1) the end user to an arbitrary URI, and it can receive a redirect (Section 2.5.2.1) through a browser request. Note that the client instance does NOT accept a push-style callback 🙅♀️. Using a redirect for both interaction start and finish is super common for web-based client software 🌐.
"interact": {
"start": ["redirect"],
"finish": {
"method": "redirect",
"uri": "https://client.example.net/return/123455",
"nonce": "LKLTI25DK82FX4T4QFZC"
}
}
In the following non-normative example, the client instance can display a user code (Section 2.5.1.3) and direct the end user to an arbitrary URI (Section 2.5.1.1), but it can't accept a redirect or a push-style callback 📺. This pattern is common for devices with robust displays that expect a secondary device to help with end-user interaction with the AS, like a set-top box showing an interaction URL as a QR code 📱.
"interact": {
"start": ["redirect", "user_code"]
}
In the following non-normative example, the client instance can't start any interaction with the end user, but the AS can push an interaction finish message (Section 2.5.2.2) when the RO's authorization arrives asynchronously 📬. This pattern is common when a service needs authorizing but the RO can be reached separately from the GNAP transaction, like through a push notification or an existing session on a secondary device 🔔.
"interact": {
"start": [],
"finish": {
"method": "push",
"uri": "https://client.example.net/return/123455",
"nonce": "LKLTI25DK82FX4T4QFZC"
}
}
If all of the following conditions are true, the AS MUST return an invalid_interaction error (Section 3.6), because the client instance won't be able to complete the request without authorization 😤:
- The client instance does not provide a suitable interaction mechanism.
- The AS cannot contact the RO asynchronously.
- The AS determines that interaction is required.
2.5.1. Start Mode Definitions 🚀
If the client instance can start interaction with the end user, it says so by sending an array of start modes under the start key 🙌. Each interaction start mode has a unique identifying name. In the array, each start mode is either a string (just the start mode name on its own) or a JSON object with the required field mode 🧩.
mode: The interaction start mode. REQUIRED.
Interaction start modes defined as objects MAY define additional parameters to be required in the object 🎀.
The start array can contain both string-type and object-type modes 💜.
This specification defines the following interaction start modes 📋:
"redirect" (string): Indicates that the client instance can direct the end user to an arbitrary URI for interaction. See Section 2.5.1.1 🔗.
"app" (string): Indicates that the client instance can launch an application on the end user's device for interaction. See Section 2.5.1.2 📱.
"user_code" (string): Indicates that the client instance can communicate a short, human-readable code to the end user, for use with a stable URI. See Section 2.5.1.3 🔤.
"user_code_uri" (string): Indicates that the client instance can communicate a short, human-readable code to the end user, for use with a short, dynamic URI. See Section 2.5.1.4 ✨.
Additional start modes can be defined in the "GNAP Interaction Start Modes" registry (Section 10.9) 🗂️.
2.5.1.1. Redirect to an Arbitrary URI ✨
okay so if the client instance can send the end user to a URI that the AS defines at runtime, it says so by putting redirect in the array under the start key 🦋 how the client actually activates that URI is out of scope for this spec, but common methods include an HTTP redirect, launching a browser on the end user's device, showing a scannable image code, or printing the URI in an interactive console 🖨️ this URI is usually hosted at the AS, but the client instance can't assume anything about its contents, how it's composed, or how it relates to the grant endpoint URI 🙈 no guessing, bestie!
"interact": {
"start": ["redirect"]
}
if this interaction mode is supported for this client instance and request, the AS returns a redirect interaction response (Section 3.3.1). the client instance manages this interaction method as described in Section 4.1.1.
see Section 11.29 for more considerations about front-channel communication techniques 💅
2.5.1.2. Open an Application-Specific URI 🎀
if the client instance can open a URI tied to an app on the end user's device, it indicates this by including app in the array under the start key 📱 the way the client figures out which app should open that URI is also out of scope here, so no spiraling about it 🌸
"interact": {
"start": ["app"]
}
if this interaction mode is supported for this client instance and request, the AS returns an app interaction response with an app URI payload (Section 3.3.2). the client instance manages this interaction method as described in Section 4.1.4.
2.5.1.3. Display a Short User Code 🔢
if the client instance can display (or otherwise communicate) a short code that a human types in to the RO, it adds user_code to the array under the start key 💜 the code gets entered at a static URI that never changes at runtime. the client has no reasonable way to send a dynamic URI to the RO, so that static URI is usually shared out of band through docs or other messaging outside of GNAP 📣 same deal as before: this URI is generally hosted at the AS, but the client instance can make no assumptions about its contents, composition, or relationship to the grant endpoint URI ✨
"interact": {
"start": ["user_code"]
}
if this interaction mode is supported for this client instance and request, the AS returns a user code as specified in Section 3.3.3. the client instance manages this interaction method as described in Section 4.1.2.
2.5.1.4. Display a Short User Code and URI 🦄
if the client instance can show a short human-entered code and a short human-entered URI to the RO, it includes user_code_uri in the array under the start key 🌈 the code gets entered at the dynamic URL given in the response. this URL is generally hosted at the AS, but the client instance can make no assumptions about its contents, composition, or relationship to the grant endpoint URI 👑
"interact": {
"start": ["user_code_uri"]
}
if this interaction mode is supported for this client instance and request, the AS returns a user code and interaction URL as specified in Section 3.3.4. the client instance manages this interaction method as described in Section 4.1.3.
2.5.2. Interaction Finish Methods 🏁
if the client instance can receive a message from the AS saying the RO has finished their interaction, it tells the AS by sending the following members of an object under the finish key 🫶
method(string): The callback method that the AS will use to contact the client instance. REQUIRED.uri(string): The URI that the AS will use to signal the client instance that the interaction has completed. This URI MAY be unique per request and MUST be hosted by or accessible to the client instance. This URI MUST be an absolute URI and MUST NOT contain any fragment component. If the client instance needs any state information tied to the front-channel interaction response, it MUST use a unique callback URI to link to that ongoing state. The allowable URIs and URI patterns MAY be restricted by the AS based on the client instance's presented key information. The callback URI SHOULD be presented to the RO during the interaction phase, before the redirect. REQUIRED for redirect and push methods.nonce(string): A unique ASCII string value used in calculating the "hash" query parameter sent to the callback URI. It must be sufficiently random to be unguessable by an attacker. It MUST be generated by the client instance as a unique value for this request. REQUIRED.hash_method(string): An identifier of a hash calculation mechanism to be used for the callback hash in Section 4.2.3, as defined in the IANA "Named Information Hash Algorithm Registry" [HASH-ALG]. If absent, the default value is sha-256. OPTIONAL.
this spec defines the following values for the method parameter. additional values can be defined in the "GNAP Interaction Finish Methods" registry (Section 10.10) 🍓
"redirect": Indicates that the client instance can receive a redirect from the end user's device after the RO has finished interacting. See Section 2.5.2.1."push": Indicates that the client instance can receive an HTTP POST request from the AS after the RO has finished interacting. See Section 2.5.2.2.
if interaction finishing is supported for this client instance and request, the AS will return a nonce (Section 3.3.5) that the client instance uses to validate the callback. All interaction finish methods MUST use this nonce so the client can verify the connection between the pending interaction request and the callback. GNAP does this through the interaction hash, defined in Section 4.2.3. All requests to the callback URI MUST be processed as described in Section 4.2.
all interaction finish methods MUST require presentation of an interaction reference to continue the grant request. so the interaction reference MUST be returned by the AS and MUST be presented by the client as described in Section 5.1. the means by which the interaction reference gets back to the client instance depend on the specific interaction finish method 🔥
2.5.2.1. Receive an HTTP Callback through the Browser 🌐
a finish method value of redirect means the client instance expects a request from the RO's browser using the HTTP method GET, as described in Section 4.2.1 💖
the client instance's URI MUST be protected by HTTPS, be hosted on a server local to the RO's browser ("localhost"), or use an application-specific URI scheme that is loaded on the end user's device.
"interact": {
"finish": {
"method": "redirect",
"uri": "https://client.example.net/return/123455",
"nonce": "LKLTI25DK82FX4T4QFZC"
}
}
requests to the callback URI MUST be processed by the client instance as described in Section 4.2.1.
since the incoming request to the callback URI comes from the RO's browser, this method is usually used when the RO and end user are the same entity. see Section 11.24 for considerations on making sure the incoming HTTP message matches the expected context of the request. see Section 11.29 for more considerations about front-channel communication techniques 🧚
2.5.2.2. Receive an HTTP Direct Callback 📮
a finish method value of push means the client instance expects a request straight from the AS using the HTTP method POST, as described in Section 4.2.2 💜
the client instance's URI MUST be protected by HTTPS, be hosted on a server local to the RO's browser ("localhost"), or use an application-specific URI scheme that is loaded on the end user's device.
"interact": {
"finish": {
"method": "push",
"uri": "https://client.example.net/return/123455",
"nonce": "LKLTI25DK82FX4T4QFZC"
}
}
requests to the callback URI MUST be processed by the client instance as described in Section 4.2.2.
since the incoming request to the callback URI comes from the AS and not from the RO's browser, this request isn't expected to have any shared session information from the start method. see Sections 11.24 and 11.23 for more considerations about back-channel and polling mechanisms like this one 🛸
2.5.3. Hints 💡
the hints key is an object describing one or more suggestions from the client instance that the AS can use to help drive user interaction ✨
this spec defines the following property under the hints key:
ui_locales(array of strings): Indicates the end user's preferred locales that the AS can use during interaction, particularly before the RO has authenticated. OPTIONAL. Section 2.5.3.1
the following subsection details requests for interaction hints. additional interaction hints can be defined in the "GNAP Interaction Hints" registry (Section 10.11) 🌸
2.5.3.1. Indicate Desired Interaction Locales 🌍
if the client instance knows the end user's locale and language preferences, it can send that info to the AS using the ui_locales field, with an array of locale strings as defined by [RFC5646] 🇫🇷
"interact": {
"hints": {
"ui_locales": ["en-US", "fr-CA"]
}
}
if possible, the AS SHOULD use one of the locales in the array, with preference going to the first item in the array that the AS supports. if none of the given locales are supported, the AS MAY use a default locale 🫶
3. Grant Response ✨
okay so once the AS gets a request from a client instance, it sends back a JSON object as the HTTP content 💖 Each possible field is broken down in the subsections below.
continue(object): tells the client instance it can keep going by making one or more continuation requests 🔁 This is REQUIRED if continuation calls are allowed for this client instance on this grant request. See Section 3.1.access_token(object / array of objects): a single access token or a set of them that the client instance can use to call the RS on behalf of the RO 🔑 This is REQUIRED if an access token is included. See Section 3.2.interact(object): says that some kind of interaction through a set of defined mechanisms needs to happen 🎀 This is REQUIRED if interaction is expected. See Section 3.3.subject(object): claims about the RO, as the AS knows and declares them 🧚 This is REQUIRED if subject information is included. See Section 3.4.instance_id(string): an identifier the client instance can use to identify itself when it makes future requests 🦋 This is OPTIONAL. See Section 3.5.error(object or string): an error code that says something has gone wrong 🙈 This is REQUIRED for an error condition. See Section 3.6.
Extensions to GNAP can add more fields too, and they get listed in the "GNAP Grant Response Parameters" registry (Section 10.12) 📋
Here's a non-normative example where the AS sends back an interaction URI (Section 3.3.1), a callback nonce (Section 3.3.5), and a continuation response (Section 3.1) 🌸
NOTE: '' line wrapping per RFC 8792
{
"interact": {
"redirect": "https://server.example.com/interact/4CF492ML\
VMSW9MKMXKHQ",
"finish": "MBDOFXG4Y5CVJCX821LH"
},
"continue": {
"access_token": {
"value": "80UPRY5NM33OMUKMKSKU",
},
"uri": "https://server.example.com/tx"
}
}
In this next non-normative example, the AS sends back a bearer access token (Section 3.2.1) with a management URI and a Subject Identifier (Section 3.4), where the identifier is an opaque one 👑
{
"access_token": {
"value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
"flags": ["bearer"],
"manage": {
"uri": "https://server.example.com/token/PRY5NM33O",
"access_token": {
"value": "B8CDFONP21-4TB8N6.BW7ONM"
}
}
},
"subject": {
"sub_ids": [ {
"format": "opaque",
"id": "J2G8G8O4AZ"
} ]
}
}
And here's one more non-normative example 🌈 The AS sends a set of Subject Identifiers (Section 3.4) all at once: an opaque identifier, an email address, and a decentralized identifier (DID). They're formatted as a set of Subject Identifiers, as defined in [RFC9493] ✨
{
"subject": {
"sub_ids": [ {
"format": "opaque",
"id": "J2G8G8O4AZ"
}, {
"format": "email",
"email": "user@example.com"
}, {
"format": "did",
"url": "did:example:123456"
} ]
}
}
Okay, this part is a must-know 🚨 The response MUST be sent as a JSON object in the HTTP response content, with Content-Type set to application/json. The only exception is when a specific response says otherwise, like an empty response with no Content-Type 💅
The AS MUST include the HTTP Cache-Control response header field [RFC9111] with its value set to "no-store" 🔒
3.1. Request Continuation 🔁
If the AS decides the client instance can continue the grant request, it responds with the continue field. That field holds a JSON object with the properties below 💖
uri(string): the URI where the client instance can make continuation requests. This URI MAY vary per request, or it MAY stay stable at the AS. It MUST be an absolute URI. The client instance MUST use this value exactly as given when it makes a continuation request (Section 5). REQUIRED.wait(integer): the number of seconds, as an integer, that the client instance MUST wait after it gets this continuation response and calls the continuation URI ⏳ The value SHOULD NOT be less than five seconds, and if the value is left out, it MUST be interpreted as five seconds. RECOMMENDED.access_token(object): a unique access token for continuing the request, called the "continuation access token" 🎟️ The value of this property MUST be an object in the format specified in Section 3.2.1. This access token MUST be bound to the client instance's key used in the request, and it MUST NOT be a bearer token. So theflagsarray of this access token MUST NOT contain the stringbearer, and thekeyfield MUST be omitted. This access token MUST NOT have amanagefield. The client instance MUST present the continuation access token in all requests to the continuation URI, as described in Section 7.2. REQUIRED.
Here's the example for this field 🧁
{
"continue": {
"access_token": {
"value": "80UPRY5NM33OMUKMKSKU"
},
"uri": "https://server.example.com/continue",
"wait": 60
}
}
This field is REQUIRED if the grant request is in the pending state, because it has the info the client instance needs to continue the request, as described in Section 5. Heads up: the continuation access token is bound to the client instance's key, so the client instance MUST sign all continuation requests with its key, as described in Section 7.3, and MUST present the continuation access token in its continuation request 🔐
3.2. Access Tokens 🔑
If the AS has successfully granted one or more access tokens to the client instance, it responds with the access_token field. That field holds either a single access token, as described in Section 3.2.1, or an array of access tokens, as described in Section 3.2.2 ✨
The client instance uses any access tokens in this response to call the RS, as described in Section 7.2 🦋
The grant request MUST be in the approved state for this field to be included in the response 👑
3.2.1. Single Access Token ✨
okay so if the client instance asked for one access token and the AS said yes 🎀, the AS replies with the access_token field. Its value is an object with the following properties 💅
value(string): this is the access token as a string. It's opaque to the client instance, so don't try to peek inside 🙈 The value MUST be limited to the token68 character set defined in Section 11.2 of [HTTP] so it can travel in HTTP headers and other protocols without extra encoding. REQUIRED.label(string): this is the label the client instance gave in the token request (Section 2.1), if it sent one 🏷️ REQUIRED for multiple access tokens or if a label was included in the single access token request. OPTIONAL for a single access token where no label was included in the request.manage(object): this is the access info for the token management API for this token 🧰 If it's provided, the client instance MAY manage its access token as described in Section 6. This management API is a function of the AS and is separate from the RS the client instance is requesting access to. OPTIONAL.access(array of objects/strings): this describes the rights attached to the token, as defined in Section 8 🔑 If it's included, it MUST reflect the rights associated with the issued access token. These rights MAY vary from what was requested by the client instance. REQUIRED.expires_in(integer): the number of seconds until the access expires ⏳ The client instance MUST NOT use the access token past this time. Note that the access token MAY be revoked by the AS or RS at any point prior to its expiration. OPTIONAL.key(object / string): this is the key the token is bound to, if it's different from the client instance's presented key 🗝️ The key MUST be an object or string in a format described in Section 7.1. The client instance MUST be able to dereference or process the key information so it can sign later requests using the access token (Section 7.2). When the key is provided by value from the AS, the token shares some security properties with bearer tokens as discussed in Section 11.38. It is RECOMMENDED that keys returned for use with access tokens be key references as described in Section 7.1.1 that the client instance can correlate to its known keys. OPTIONAL.flags(array of strings): a set of flags that show attributes or behaviors of the access token the AS issued 🚩 OPTIONAL.
The value of the manage field is an object with the following properties 🧚
uri(string): the URI of the token management API for this access token 🌐 This URI MUST be an absolute URI. This URI MUST NOT include the value of the access token being managed or the value of the access token used to protect the URI. This URI SHOULD be different for each access token issued in a request. REQUIRED.access_token(object): a unique access token for continuing the request, called the "token management access token" 🫶 The value of this property MUST be an object in the format specified in Section 3.2.1. This access token MUST be bound to the client instance's key used in the request (or its most recent rotation) and MUST NOT be a bearer token. As a consequence, the flags array of this access token MUST NOT contain the string bearer, and thekeyfield MUST be omitted. This access token MUST NOT have amanagefield. This access token MUST NOT have the same value as the token it is managing. The client instance MUST present the continuation access token in all requests to the continuation URI as described in Section 7.2. REQUIRED.
The values of the flags field defined by this specification are as follows 🦋
- "bearer": a flag saying whether the token is a bearer token, meaning it isn't bound to a key and proofing mechanism 🫠 If the bearer flag is present, the access token is a bearer token, and the
keyfield in this response MUST be omitted. See Section 11.9 for additional considerations on the use of bearer tokens. - "durable": a flag that hints at how the AS behaves on token rotation 🌸 If this flag is present, the client instance can expect a previously issued access token to keep working after it has been rotated (Section 6.1) or the underlying grant request has been modified (Section 5.3), which results in new access tokens being issued. If this flag is omitted, the client instance can expect that a given access token could stop working after token rotation or grant request modification. Note that a token flagged as durable can still expire or be revoked through any normal means.
Flag values MUST NOT be included more than once.
Additional flags can be defined by extensions using the "GNAP Access Token Flags" registry (Section 10.4) 🧩
If the bearer flag and the key field in this response are both omitted, the token is bound to the key the client instance used (Section 2.3) in its request for access 🔐 If the bearer flag is omitted and the key field is present, the token is bound to the key and proofing mechanism indicated in the key field. How the AS decides to bind an access token to a key other than the one the client instance presented is out of scope for this specification, but common practices include pre-registering specific keys in a static fashion.
The client software MUST reject any access token where the flags field contains the bearer flag and the key field is present with any value. 🚫
Here's a non-normative example of a single access token bound to the client instance's key from the initial request 🍓 It has a management URI and access to three described resources (one using an object and two described by reference strings).
NOTE: '' line wrapping per RFC 8792
"access_token": {
"value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
"manage": {
"uri": "https://server.example.com/token/PRY5NM33O",
"access_token": {
"value": "B8CDFONP21-4TB8N6.BW7ONM"
}
},
"access": [
{
"type": "photo-api",
"actions": [
"read",
"write",
"dolphin"
],
"locations": [
"https://server.example.net/",
"https://resource.local/other"
],
"datatypes": [
"metadata",
"images"
]
},
"read", "dolphin-metadata"
]
}
Next up is a non-normative example of a single bearer access token with access to two described resources 🌈
"access_token": {
"value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
"flags": ["bearer"],
"access": [
"finance", "medical"
]
}
If the client instance requested a single access token (Section 2.1.1), the AS MUST NOT respond with the structure for multiple access tokens 🙅♀️
3.2.2. Multiple Access Tokens 👑
okay so if the client instance asked for multiple access tokens and the AS granted at least one of them 💫, the AS responds with the access_token field. Its value is a JSON array, and the members are distinct access tokens as described in Section 3.2.1. Each object MUST have a unique label field, matching the token labels the client instance picked in the request for multiple access tokens (Section 2.1.2) 🏷️
Here's a non-normative example where two tokens are issued under the names token1 and token2, and only the first one has a management URI attached 🦋
NOTE: '' line wrapping per RFC 8792
"access_token": [
{
"label": "token1",
"value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
"manage": {
"uri": "https://server.example.com/token/PRY5NM33O",
"access_token": {
"value": "B8CDFONP21-4TB8N6.BW7ONM"
}
},
"access": [ "finance" ]
},
{
"label": "token2",
"value": "UFGLO2FDAFG7VGZZPJ3IZEMN21EVU71FHCARP4J1",
"access": [ "medical" ]
}
}
Each access token lines up with one of the objects in the access_token array of the client instance's request (Section 2.1.2) 💜
The AS MAY refuse to issue one or more of the requested access tokens for any reason 🙈 In that case, the refused token is left out of the response, and all the other issued access tokens are included under their respective requested labels. If the client instance requested multiple access tokens (Section 2.1.2), the AS MUST NOT respond with a single access token structure, even if only one access token is granted. Instead, the AS MUST respond with a structure for multiple access tokens containing that one access token 💅
Here's the example for that situation 🍓
"access_token": [
{
"label": "token2",
"value": "8N6BW7OZB8CDFONP219-OS9M2PMHKUR64TBRP1LT0",
"manage": {
"uri": "https://server.example.com/token/PRY5NM33O",
"access_token": {
"value": "B8CDFONP21-4TB8N6.BW7ONM"
}
},
"access": [ "fruits" ]
}
]
The parameters of each access token are kept separate ✨ For example, each access token is expected to have a unique value and (if present) label, and each access token likely has different access rights attached. Each access token could also be bound to different keys with different proofing mechanisms 🌸
3.3. Interaction Modes ✨
Okay bestie, so if the client instance said it can do interaction in its request (Section 2.5) AND the AS decided interaction is both supported and actually needed, the AS replies with any of these values in the interact field 💖 There's no preference order, so the client instance picks whichever ones it wants 🫶 Every supported method goes in the same interact object.
redirect(string): redirect to an arbitrary URI. REQUIRED if the redirect interaction start mode is possible for this request. See Section 3.3.1.app(string): launch of an application URI. REQUIRED if the app interaction start mode is possible for this request. See Section 3.3.2.user_code(string): display a short user code. REQUIRED if the user_code interaction start mode is possible for this request. See Section 3.3.3.user_code_uri(object): display a short user code and URI. REQUIRED if the user_code_uri interaction start mode is possible for this request. Section 3.3.4finish(string): a unique ASCII string value the AS gives as a nonce 🎀. The client instance uses it to verify the callback once interaction is done. REQUIRED if the interaction finish method the client instance asked for is possible for this request. See Section 3.3.5.expires_in(integer): the number of integer seconds after which this set of interaction responses expires and can't be used by the client instance anymore 😭. If the interaction methods expire, the client instance MAY restart the interaction process for this grant request by sending an update (Section 5.3) with a new interaction request field (Section 2.5). OPTIONAL. If it's left out, the interaction response modes don't expire, but the AS MAY invalidate them at any time.
Extra interaction mode responses can be defined in the "GNAP Interaction Mode Responses" registry (Section 10.13) 🌈
The AS MUST NOT respond with any interaction mode the client instance didn't indicate in its request, and the AS MUST NOT respond with any interaction mode the AS doesn't support 🙅♀️. Since interaction responses include secret or unique information, the AS SHOULD respond to each interaction mode only once in an ongoing request, especially if the client instance changes its request (Section 5.3). Lowkey this one matters a LOT.
The grant request MUST be in the pending state for this field to show up in the response 💅
3.3.1. Redirection to an Arbitrary URI
If the client instance says it can redirect to an arbitrary URI (Section 2.5.1.1) and the AS supports this mode for the client instance's request, the AS responds with the "redirect" field 🦋. That field is a string holding the URI the end user should visit. This URI MUST be unique for the request and MUST NOT contain any security-sensitive information, like user identifiers or access tokens 🙈.
"interact": {
"redirect": "https://interact.example.com/4CF492MLVMSW9MKMXKHQ"
}
The URI that comes back depends on the AS, but the URI itself MAY be totally separate from the grant endpoint URI the client instance uses to request access (Section 2). That lets an AS keep its user-interaction stuff apart from its backend security stuff, which is iconic 👑. The AS will need to look up the specific grant request and its info from the URI alone. If the AS doesn't directly host the functionality reached through the redirect URI, then how that interaction functionality talks to the rest of the AS is out of scope for this specification.
The client instance sends the end user to the URI so they can interact with the AS. The client instance MUST NOT alter the URI in any way 🚫. How the client instance sends the end user there is out of scope for this specification, but common methods include an HTTP redirect, launching the system browser, displaying a scannable code, or printing the URI in an interactive console. See details of the interaction in Section 4.1.1.
3.3.2. Launch of an Application URI
If the client instance says it can launch an application URI (Section 2.5.1.2) and the AS supports this mode for the client instance's request, the AS responds with the "app" field 🚀. That's a string holding the URI the client instance should launch. This URI MUST be unique for the request and MUST NOT contain any security-sensitive information, like user identifiers or access tokens 🙈.
"interact": {
"app": "https://app.example.com/launch?tx=4CF492MLV"
}
How the launched application talks to the AS is out of scope for this specification.
The client instance launches the URI however makes sense on its platform. How it does that is out of scope for this specification. The client instance MUST NOT alter the URI in any way 🚫. The client instance MAY try to detect whether an installed application will handle the URI before it tries to launch the application URI 🔍. See details of the interaction in Section 4.1.4.
3.3.3. Display of a Short User Code
If the client instance says it can display a short, user-typeable code (Section 2.5.1.3) and the AS supports this mode for the client instance's request, the AS responds with a "user_code" field 🍓. That field is a string with a unique short code the user can type into a web page. To make it easy to use, this string MUST consist only of characters that are easy for the end user to type (like ASCII letters or numbers) and MUST be processed by the AS in a case-insensitive manner (see Section 4.1.2). The string MUST be randomly generated so that an attacker can't guess it within the time it's accepted 🔐. The time this code is accepted SHOULD be short-lived, like several minutes. It is RECOMMENDED that this code be between six and eight characters long ✨.
"interact": {
"user_code": "A1BC3DFF"
}
The client instance MUST communicate the "user_code" value to the end user somehow, like showing it on a screen or reading it out loud 📣. The AS's interaction component uses this code to figure out which pending grant request it is. It is NOT an authentication factor for the RO 🙅♀️.
The URI where the end user is supposed to type the code MUST be stable, because the client instance isn't expected to be able to send a dynamic URI to the end user at runtime 💜.
This interaction mode is built for a secondary device, so the client instance is not expected to redirect the end user to the URI where the code gets entered 📱. If the client instance can communicate a short arbitrary URI to the end user for use with the user code, the client instance SHOULD use the "user_code_uri" mode (Section 2.5.1.4) instead. If the client instance can communicate a long arbitrary URI to the end user, like through a scannable code, the client instance SHOULD use the "redirect" mode (Section 2.5.1.1) for that purpose, either instead of or in addition to the user code mode 🌸.
See details of the interaction in Section 4.1.2.
3.3.4. Display of a Short User Code and URI
If the client instance says it can display a short, user-typeable code (Section 2.5.1.3) and the AS supports this mode for the client instance's request, the AS responds with a "user_code_uri" object that has the following members 🎀.
code (string): a unique short code the end user can type into a provided URI. To make it easy to use, this string MUST consist only of characters that are easy for the end user to type (like ASCII letters or numbers) and MUST be processed by the AS in a case-insensitive manner (see Section 4.1.3). The string MUST be randomly generated so that an attacker can't guess it within the time it's accepted. The time this code is accepted SHOULD be short-lived, like several minutes. It is RECOMMENDED that this code be between six and eight characters long. REQUIRED.
uri (string): the interaction URI the client instance will send the RO to. This URI MUST be short enough to be communicated to the end user by the client instance. It is RECOMMENDED that this URI be short enough for an end user to type in by hand. The URI MUST NOT contain the code value. This URI MUST be an absolute URI. REQUIRED.
"interact": {
"user_code_uri": {
"code": "A1BC3DFF",
"uri": "https://s.example/device"
}
}
The client instance MUST communicate the "code" to the end user somehow, like showing it on a screen or reading it out loud 📣. The AS's interaction component uses this code to figure out which pending grant request it is. It is NOT an authentication factor for the RO 🙅♀️.
The client instance MUST also communicate the URI to the end user 💅. Since the end user is expected to keep going on a secondary device, the URI needs to be short enough that they can type or copy it onto that device without messing up 🙌.
The URI that comes back depends on the AS, but the URI itself MAY be totally separate from the grant endpoint URI the client instance uses to request access (Section 2), so an AS can keep its user-interaction functionality apart from its backend security functionality 🦋. If the AS doesn't directly host the functionality reached through the given URI, then how that interaction functionality talks to the rest of the AS is out of scope for this specification.
See details of the interaction in Section 4.1.2.
3.3.5. Interaction Finish
If the client instance says it can receive a post-interaction redirect or push at a URI (Section 2.5.2) and the AS supports this mode for the client instance's request, the AS responds with a finish field 🔔. That field holds a nonce the client instance will use to validate the callback, as defined in Section 4.2.
"interact": {
"finish": "MBDOFXG4Y5CVJCX821LH"
}
When the interaction is complete, the AS's interaction component MUST contact the client instance using the means defined by the finish method, as described in Section 4.2 📬.
If the AS returns the finish field, the client instance MUST NOT continue a grant request until it receives the associated interaction reference on the callback URI 🛑. See details in Section 4.2.
3.4. Returning Subject Information ✨
Okay so, if the client instance asks for info about the RO and the AS says yes, the AS drops the approved details into the subject response field 💖. But there's a big rule here: the AS MUST return the subject field only when it's sure the RO and the end user are the same party 🙈. The AS can get that certainty through some kinds of interaction with the RO (see Section 4) 🔍.
The subject field is an object with these properties 🎀
sub_ids(array of objects): a list of Subject Identifiers for the RO, the kind defined by [RFC9493] 🆔. It's REQUIRED if you're returning Subject Identifiers.assertions(array of objects): a list of assertions, each one an object that looks like the assertion object described below 📜. It's REQUIRED if you're returning assertions.updated_at(string): a timestamp written as a date string in the format from [RFC3339], showing when the identified account was last updated 🕰️. The client instance MAY use this to decide whether it should go fetch fresher profile info through an identity API. The definition of that identity API is out of scope for this spec. It's RECOMMENDED.
Assertion objects have two fields 💅
format(string): the assertion format. The possible formats are listed in Section 3.4.1. You can define extra formats in the "GNAP Assertion Formats" registry (Section 10.6). It's REQUIRED.value(string): the assertion value, as the JSON string serialization of the assertion ✨. It's REQUIRED.
Here's a non-normative example (basically a vibe check, not the official rulebook 🙃) with an opaque identifier and an OpenID Connect ID Token:
"subject": {
"sub_ids": [ {
"format": "opaque",
"id": "XUT2MFM1XBIKJKSDU8QM"
} ],
"assertions": [ {
"format": "id_token",
"value": "eyj..."
} ]
}
Subject Identifiers the AS sends back SHOULD uniquely identify the RO at that AS 🎯. Some of them are opaque to the client instance, like the subject from an issuer and subject pair 🕶️. Others, like an email address or phone number, are meant to let the client instance match the identifier up with other account info it already has 📧. The client instance MUST NOT request or use any returned Subject Identifiers for communication purposes (see Section 2.2) 📵. A Subject Identifier that looks like an email address or phone number only tells you who the RO is to the AS. It does NOT mean the AS checked that the email or phone number is actually good for reaching the current user 🙅♀️. If you want that kind of info, the client instance MUST use an identity protocol to request and receive additional identity claims 🪪. The details of that identity protocol and its schema are outside the scope of this spec.
The AS MUST make sure the returned subject info represents the RO 🫶. In most cases, the AS will also make sure it represents the end user who authenticated interactively at the AS 🔐. The AS SHOULD NOT reuse Subject Identifiers across multiple different ROs 🙅♀️.
The sub_ids and assertions response fields are independent of each other 🦋. So a returned assertion MAY use a different Subject Identifier than the other assertions and Subject Identifiers in the response. Still, every Subject Identifier and assertion returned MUST refer to the same party 👯♀️.
The client instance MUST interpret all subject information in the context of the AS it came from, as discussed in Section 6 of [SP80063C] 🧠. For example, one AS could return the email identifier "user@example.com" for one RO, and a totally different AS could return that same "user@example.com" for a completely different RO 😳. A client instance talking to both ASes has to tell these two accounts apart by tracking which AS each identifier came from. It can't assume either AS has a canonical claim on the identifier unless it has extra configuration and trust agreements in place 🚫.
Okay, this part is serious. If a client doesn't keep track of which AS an identifier came from, a rogue AS could use that to take over a targeted account that a different AS asserted 🚨. Don't skip this one, bestie.
Extensions to this spec MAY define additional response properties in the "GNAP Subject Information Response Fields" registry (Section 10.14) 🧩.
The grant request MUST be in the approved state for the AS to return this field in the response ✅.
See Section 11.30 for the things the client instance has to think about when it accepts and processes assertions from the AS 🛡️.
3.4.1. Assertion Formats
The following assertion formats are defined in this specification 📋
id_token: an OpenID Connect ID Token [OIDC], in JSON Web Token (JWT) compact format, as a single string 🎟️.saml2: a SAML 2.0 assertion [SAML2], encoded as a single base64url string with no padding 🍓.
3.5. Returning a Dynamically Bound Client Instance Identifier ✨
A lot of the client instance's request can be sent as either a value or a reference 🔁. Using a reference instead of a value lets the client instance optimize its requests to the AS 🚀.
Some references, like the one for the client instance's identity (Section 2.3.1) or the requested resources (Section 8.1), can be managed ahead of time through an admin console or developer portal that the AS or RS provides 🗂️. The developer of the client software can bake these values into their code, which makes the request more efficient and compact 💻.
If it wants, the AS MAY also generate an instance identifier on the fly and return it to the client instance in the response. That helps the client instance interact with the same client instance over time 🔄. The client instance SHOULD use this instance identifier in future requests instead of sending the associated data values in the client field 🫶.
Dynamically generated client instance identifiers are string values that the client instance MUST protect as secrets 🤫. Instance identifier values MUST be unguessable, and they MUST NOT contain any information that would compromise any party if revealed 🔐. The client instance can't see inside them, because their content is decided by the AS 👀. The instance identifier MUST be unique per client instance at the AS 💜.
instance_id(string): a string used to stand in for the information in theclientobject, which the client instance can use in a future request, as described in Section 2.3.1 🪪. It's OPTIONAL.
Here's a non-normative example showing an instance identifier next to an issued access token 🌸
{
"instance_id": "7C7C4AZ9KHRS6X63AJAO",
"access_token": {
"value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0"
}
}
3.6. Error Response 💀
If the AS decides the request can't be completed for any reason, it responds to the client instance with an error field in the response message 😭. That field is either an object or a string.
When it comes back as an object, the object has these fields 🎀
code(string): a single ASCII error code that defines the error 🔤. The value MUST be defined in the "GNAP Error Codes" registry (Section 10.15). It's REQUIRED.description(string): a human-readable description of the error, meant for the developer of the client 📝. The implementation chooses the value. It's OPTIONAL.
This spec defines the following code values 💅
"invalid_request": the request is missing a required parameter, has an invalid parameter value, or is otherwise malformed 🙈."invalid_client": the request came from a client the AS didn't recognize or allow, or the client's signature validation failed 🚫."invalid_interaction": the client instance gave an interaction reference that's wrong for this request, or the interaction modes in use have expired ⏰."invalid_flag": the flag configuration isn't valid 🚩."invalid_rotation": the token rotation request isn't valid 🔄."key_rotation_not_supported": the AS doesn't allow rotating this access token's key 🔑."invalid_continuation": the continuation of the referenced grant couldn't be processed ⏭️."user_denied": the RO said no 🙅♀️."request_denied": the request was denied for a reason that isn't specified 🤷♀️."unknown_user": the user in the request isn't known to the AS, or doesn't match the user who showed up during interaction 👤."unknown_interaction": the interaction integrity couldn't be established 🧩."too_fast": the client instance didn't respect the timeout in the wait response before its next call 🏃♀️💨."too_many_attempts": a limit on the total number of reasonable attempts has been reached. The number is either set statically or adjusted by the AS based on runtime conditions 🛑.
More error codes can be defined in the "GNAP Error Codes" registry (Section 10.15) 🌈.
For example, if the RO denied the request during interaction with the AS, the AS would return this error when the client instance tries to continue the grant request 😤
{
"error": {
"code": "user_denied",
"description": "The RO denied the request"
}
}
The AS MAY instead return the error as just a code, and give the error as a string 🧵. Since description isn't meant to be machine-readable, this response counts as functionally equivalent to the one above for the client software's understanding 🤖
{
"error": "user_denied"
}
If an error state is reached but the grant is in the pending state (so the client instance can still continue), the AS MAY include the continue field in the response along with the error, as defined in Section 3.1 🔁. That lets the client instance change its request for access, which could lead to prompting the RO again 🙋♀️. Other fields MUST NOT be included in the response 🚫.
4. Determining Authorization and Consent 💅
okay bestie, so when the client instance makes its very first request (Section 2) to the AS for delegated access, it can ask for a bunch of different things back 🦋:
- the access being requested, in the
access_tokenrequest parameter - the subject information being requested, in the
subjectrequest parameter - any additional requested information defined by extensions of this protocol
When the grant request is in the processing state, the AS figures out what authorizations and consents it needs to fulfill this delegation 🔍. How the AS actually does that is out of scope for this document, so that part is up to each AS. GNAP does have some common patterns for getting these requirements met, though, like info sent by the client instance, info gathered through the interaction process, and info supplied by external parties ✨. Every AS can come up with its own policies and processes for deciding when and how to gather the authorizations and consent, and for how those get applied to the grant request.
To help the AS with this, the client instance sends info about the actions the client software can take 🎀, including:
- starting interaction with the end user, in the
interactrequest parameter - receiving notification that interaction with the RO has concluded, in the
interactrequest parameter - any additional capabilities defined by extensions of this protocol
The client instance can also hand info straight to the AS in its request 💜. It can send a few kinds of things, including:
- the identity of the client instance, known from the keys or identifiers in the
clientrequest parameter - the identity of the end user, in the
userrequest parameter - any additional information presented by the client instance in the request, as defined by extensions of this protocol
The AS processes all of this presented info in the context of the client instance's request, and it can only trust the info as much as it trusts how that request was presented and what surrounds it 🧐. If the AS decides the info in the initial request is enough to grant the requested access, the AS MAY move the grant request to the approved state and return the results right away in its response (Section 3), with access tokens and subject information 🎉.
If the AS decides it needs extra authorization at runtime, it can either deny the request outright (if there's no way to recover) or move the grant request to the pending state and use whatever tools it has to collect that authorization from the right ROs 👑. Some of those tools are:
- starting interaction with the end user, facilitated by the client software, such as a redirection or user code
- challenging the client instance through a challenge-response mechanism
- requesting that the client instance present specific additional information, such as a user's credential or an assertion
- contacting an RO through an out-of-band mechanism, such as a push notification
- executing an auxiliary software process through an out-of-band mechanism, such as querying a digital wallet
GNAP leaves the process of gathering authorization and consent deliberately flexible 🌈, so it can work across a huge range of deployments, interactions, and methodologies. The AS can gather consent from the RO, or it can apply the RO's policy, depending on what access was requested. Sometimes the AS can tell which RO needs to be prompted for consent based on what the client instance asked for, like a specific RS record, an identified subject, or a request that needs specific access such as approval by an administrator 🧑💼. Other times, the request just goes to whichever RO is present when consent is being gathered. This pattern is super common when the end user gets sent to the AS for an interactive session, because during that session the end user takes on the role of the RO. In those cases, the end user is delegating their own access as RO to the client instance 🫶.
The client instance can say it's able to help with interaction with the end user, another party, or another piece of software through its interaction start request (Section 2.5.1) 🦋. Here the AS usually needs to talk directly to the end user to confirm their identity, confirm their status as an RO, and collect their consent. If the AS has decided authorization is needed, and it can support one or more of the requested interaction start methods, it returns the matching interaction start responses (Section 3.3) 💖. The client instance SHOULD start one or more of these interaction methods (Section 4.1) so the grant request can actually get granted. If more than one interaction start method is available, how the client picks which ones to follow is out of scope for this specification.
After starting interaction, the client instance can then make a continuation request (Section 5). It can do this in response to a signal that the interaction has finished (Section 4.2), after time-based polling, or through some other method defined by an extension of this specification via the "GNAP Interaction Mode Responses" registry (Section 10.13) ✨.
If the grant request isn't in the approved state, the client instance can repeat the interaction process by sending a grant update request (Section 5.3) with new interaction methods (Section 2.5) 🔁.
The client instance MUST use each interaction method once at most if a response can be detected 🚫. The AS MUST handle any interact request as a one-time-use mechanism, and it SHOULD apply suitable timeouts to any interaction start methods it provides, including user codes and redirection URIs ⏰. The client instance SHOULD apply suitable timeouts to any interaction finish method too 💅.
Now, client software on disadvantaged networks needs some grace 🥺, so the AS MAY allow the same interaction method to be processed multiple times, as long as the AS can determine that the request is from the same party and the results are idempotent. For example, say a client instance launches a redirect to the AS but never gets a response in a reasonable amount of time. The client software can launch the redirect again, assuming it never reached the AS in the first place 🙈. But if the AS gets both requests, it could accidentally process them separately and leave the client instance in an undefined state 💀. So if the AS can tell that both requests come from the same origin or under the same session, and both arrived before any other state change to the grant happened, the AS can reasonably conclude the first response was never received, and the same response can be sent back to the client instance 🌸.
If the AS has a way to contact the RO directly, it can do that without bringing the client instance into its consent-gathering process 📲. For example, the AS could push a notification to a known RO, and the RO could approve the pending request whenever they get to it, asynchronously. These interactions can happen through an interface on the AS itself (like a hosted web page), through another application (like something installed on the RO's device), through a messaging fabric, or through any other means 🍓.
When the AS is interacting with an RO, it can use different strategies to determine whether the requested grant is authorized, including:
- authenticating the RO through a local account or some other means, such as federated login
- validating the RO through presentation of claims, attributes, or other information
- prompting the RO for consent for the requested delegation
- describing to the RO what information is being released, to whom, and for what purpose
- providing warnings to the RO about potential attacks or negative effects of allowing the information
- allowing the RO to modify the client instance's requested access, including limiting or expanding that access
- providing the RO with artifacts such as receipts, to facilitate an audit trail of authorizations
- allowing the RO to deny the requested delegation
The AS is also allowed to request authorization from more than one RO, if it thinks that's a good idea 🧠. For example, a medical record might need to be released by both an attending nurse and a physician, or both owners of a bank account need to sign off on a transfer request 🏦. The AS could also require N of M possible ROs to approve a given request. Sometimes the AS might even figure out that the end user present during the interaction isn't the right RO for a given request, and then reach out to the right RO asynchronously 🙌.
The RO is also allowed to set up an automated policy at the AS that decides which kinds of end users can get access to the resource and under which conditions 📜. For instance, that condition might require the end user to log in and accept the RO's legal provisions. Or, client software could be acting without an end user at all, and the RO's policy could allow access tokens to be issued to specific instances of that client software without any human interaction 🤖.
GNAP supports all of these cases 💖. The details of how each one gets implemented, and the methods for figuring out which ROs or related policies a given request requires, are out of scope for this specification.
4.1. Starting Interaction with the End User 🎀
When a grant request is in the pending state, the interaction start methods the client instance sent can be used to facilitate interaction with the end user 🦋. To kick off an interaction start method listed in the interaction start responses (Section 3.3) from the AS, the client instance follows the steps defined for that interaction start mode. The actions the client instance needs to take for the interaction start modes defined in this specification are covered in the subsections below. Interaction start modes defined in extensions to this specification MUST define the expected actions of the client software when that interaction start mode is used 📋.
If the client instance doesn't start an interaction start mode within an amount of time the AS decides on, the AS MUST reject attempts to use those interaction start modes ⏳. If the client instance has already begun one interaction start mode and that interaction has been completed successfully, the AS MUST reject attempts to use any other interaction start modes 🚫. For example, if a user code has been entered successfully for a grant request, the AS will need to reject requests to an arbitrary redirect URI on that same grant request. That's how it prevents an attacker from capturing and altering an active authorization process 🕵️♀️💀.
4.1.1. Interaction at a Redirected URI 🦋
okay so when the end user gets sent to an arbitrary URI through the redirect mode (Section 3.3.1), the client instance is the one that helps get that URI open in the end user's web browser 🌐. It could launch the URI through the system browser, drop a clickable link, bounce the user along with HTTP response codes, or show the URI in a form the end user can use to launch it, like a multidimensional barcode 📱. Every option ends with an HTTP GET request to the URI, and the page that loads is expected to talk to the end user directly through an HTTP user agent 💅. With this method it's super common (though not required) for the RO to be the same person as the end user, since the client instance has to hand that redirection URI over to them 🫶.
In many cases the URI points to a web page hosted at the AS, so the AS can authenticate the end user as the RO and collect consent right there 🔐. The URI value is what identifies the grant request being authorized 🎯. If the URI can't be matched to a currently active request, the AS MUST display an error to the RO and MUST NOT attempt to redirect the RO back to any client instance, even if a redirect finish method is supplied (Section 2.5.2.1) 🙅♀️. If the URI isn't hosted by the AS directly, the way the AS and the service behind that URI communicate is out of scope for this specification 🤷♀️.
The client instance MUST NOT modify the URI when launching it. In particular, the client instance MUST NOT add any parameters to the URI 🚫. The URI MUST be reachable from the end user's browser, though it MAY be opened on a separate device from the client instance itself 📲. The URI MUST be accessible from an HTTP GET request, and it MUST be protected by HTTPS, be hosted on a server local to the RO's browser ("localhost"), or use an application-specific URI scheme that is loaded on the end user's device 🔒✨.
4.1.2. Interaction at the Static User Code URI 🔢
when the end user is directed to enter a short code through the user_code mode (Section 3.3.3), the client instance shares the user code and tells the end user to type it in at an associated URI 💌. The client instance MAY format the user code so it's easier to remember and pass along, as long as the formatting doesn't change the value accepted at the user code URI 🧠. For example, if a client instance gets the user code "A1BC3DFF", it could display it as "A1BC 3DFF", splitting the long string into two shorter chunks 🍓.
When processing input codes, the AS MUST transform the input string to remove invalid characters. In the example above, the space between the two parts gets stripped out once it's entered into the interactive form at the user code URI 🧹. The AS MUST also treat user input as case insensitive, so if someone types "a1bc 3DFF", the AS will treat it exactly the same as "A1BC3DFF" 🙌. To make this work, it is RECOMMENDED that the AS use only ASCII letters and numbers as valid characters for the user code 🔤.
It is RECOMMENDED that the AS choose characters that are easy to copy and type without any confusion 👀. Some glyphs have more than one Unicode code point for the same visual character, so the end user could end up typing a different character than the one the AS handed back 😭. For more on internationalized character strings, see [RFC8264] 🌏.
This mode is designed for when the client instance can't communicate or help launch an arbitrary URI 🙅♀️. The associated URI could be statically configured in the client instance or written in the client software's documentation. Because of that, these URIs SHOULD be short 📏. The user code URI MUST be reachable from the end user's browser, though it's usually opened on a separate device from the client instance itself 📱. The URI MUST be accessible from an HTTP GET request, and it MUST be protected by HTTPS, be hosted on a server local to the RO's browser ("localhost"), or use an application-specific URI scheme that is loaded on the end user's device 🔐.
In many cases the URI points to a web page hosted at the AS, so the AS can authenticate the end user as the RO and collect consent interactively 🥰. The value of the user code identifies the grant request being authorized. If the user code can't be matched to a currently active request, the AS MUST display an error to the RO and MUST NOT attempt to redirect the RO back to any client instance, even if a redirect finish method is supplied (Section 2.5.2.1) 🚫. If the interaction component at the user code URI isn't hosted by the AS directly, the way the AS and this URI communicate, including how the user code itself gets passed along, is out of scope for this specification 🤷♀️.
When the RO enters this code at the user code URI, the AS MUST uniquely identify the pending request the code was associated with 🎯. If the AS doesn't recognize the entered code, the interaction component MUST display an error to the user 💀. If the AS detects too many unrecognized code entry attempts, the interaction component SHOULD display an error to the user saying there were too many attempts, and it MAY take additional actions such as slowing down the input interactions 🐢. The user should be warned as they get close to that error state, if possible ⚠️.
4.1.3. Interaction at a Dynamic User Code URI 🌈
When the end user is directed to enter a short code through the user_code_uri mode (Section 3.3.4), the client instance shares both the user code and the associated URI, then tells the end user to enter that code at the URI 💜. The client instance MAY format the user code so it's easier to remember and transfer, as long as the formatting doesn't alter the value accepted at the user code URI ✨. For example, a client instance receiving the user code "A1BC3DFF" could show it as "A1BC 3DFF", breaking the long string into two shorter strings 🎀.
When processing input codes, the AS MUST transform the input string to remove invalid characters. In the example above, the space between the two parts gets removed when it's entered into the interactive form at the user code URI 🧼. The AS MUST also treat user input as case insensitive. So if the user types "a1bc 3DFF", the AS will treat it the same as "A1BC3DFF" 💅. To facilitate this, it is RECOMMENDED that the AS use only ASCII letters and numbers as valid characters for the user code 🔡.
This mode is used when the client instance can't help launch a complicated arbitrary URI, but it can pass along simple values like URIs 🧚. Because of that, these URIs SHOULD be short enough for the end user to actually type, like a total length of 20 characters or fewer ⌨️. The client instance MUST NOT modify the URI when communicating it to the end user. In particular, the client instance MUST NOT add any parameters to the URI 🙅♀️. The user code URI MUST be reachable from the end user's browser, though it is usually opened on a separate device from the client instance itself 📱. The URI MUST be accessible from an HTTP GET request, and it MUST be protected by HTTPS, be hosted on a server local to the RO's browser ("localhost"), or use an application-specific URI scheme that is loaded on the end user's device 🔒.
In many cases the URI points to a web page hosted at the AS, so the AS can authenticate the end user as the RO and interactively collect consent 🫶. The value of the user code identifies the grant request being authorized. If the user code can't be matched to a currently active request, the AS MUST display an error to the RO and MUST NOT attempt to redirect the RO back to any client instance, even if a redirect finish method is supplied (Section 2.5.2.1) 😵. If the interaction component at the user code URI isn't hosted by the AS directly, the communication between the AS and this URI, including how the user code itself gets communicated, is out of scope for this specification 🤷♀️.
When the RO enters this code at the given URI, the AS MUST uniquely identify the pending request the code was associated with 🔍. If the AS doesn't recognize the entered code, the interaction component MUST display an error to the user 🙈. If the AS detects too many unrecognized code entry attempts, the interaction component SHOULD display an error to the user about too many attempts, and it MAY take additional actions such as slowing down the input interactions 🐌. The user should be warned as that error state gets close, if possible 🚨.
4.1.4. Interaction through an Application URI 📱
When the client instance is directed to launch an application through the app mode (Section 3.3.2), the client launches the URI in whatever way fits the system, like a deep link or a custom URI scheme registered to a mobile application 🚀. The way the AS and the launched application communicate and handle any of the required actions is out of scope for this specification 🫠.
4.2. Post-Interaction Completion 🏁
If an interaction "finish" method (Section 3.3.5) is associated with the current request, the AS MUST follow the appropriate method once the interaction is complete, so it can signal the client instance to keep going. The exception is a few limited error cases, which are covered below 🚦. If there's no finish method available, the AS SHOULD tell the RO to return to the client instance when they're done 💁♀️. In that case, the client instance is expected to poll the continuation endpoint as described in Section 5.2 🔁.
The AS MUST create an interaction reference and associate that reference with the current interaction and the underlying pending request 🔗. The interaction reference value is an ASCII string made up of only unreserved characters, per Section 2.3 of [RFC3986] 🔤. The interaction reference value MUST be sufficiently random so that an attacker can't guess it 🎲🕵️♀️. The interaction reference MUST be one-time-use to prevent interception and replay attacks 🛡️💥.
The AS MUST calculate a hash value based on the client instance, AS nonces, and the interaction reference, as described in Section 4.2.3 🧮. The client instance uses this value to validate the "finish" call 🫶.
All interaction finish methods MUST define a way to convey the hash and interaction reference back to the client instance 📬. When an interaction finish method is used, the client instance MUST present the interaction reference back to the AS as part of its continuation request (Section 5.1) 🎀.
Heads up, though: in many error cases, like when the RO has denied access, the AS still runs the "finish" method anyway 😮. This lets the client instance recover from the error by changing its request or giving the AS more info directly in a continuation request 💪✨. The AS MUST NOT follow the "finish" method in the following circumstances:
- The AS has determined that any URIs involved with the finish method are dangerous or blocked 🚫☠️.
- The AS cannot determine which ongoing grant request is being referenced 🤔.
- The ongoing grant request has been canceled or otherwise blocked 🥀.
4.2.1. Completing Interaction with a Browser Redirect to the Callback URI
okay so bestie, this is the redirect finish method 🦋 (the one from Sections 2.5.2.1 and 3.3.5). the AS basically tells the client instance "hey, we're done here!" by sending the RO (that's the end user, in their browser) back to the client instance's redirect URI. literally just a browser trip home 🏠✨
the AS keeps this redirect secure by tacking the hash and the interaction reference on as query parameters to that redirect URI 🔐
hash: the interaction hash value, which you can calculate the way Section 4.2.3 explains. REQUIRED. 💖
interact_ref: the interaction reference the AS generated for this specific interaction. REQUIRED. 💖
how the RO actually gets sent to this URI is outside the scope of this spec 🙈 but the usual moves are redirecting from a web page or launching the system browser with the target URI. and if you're wondering which HTTP status code to use when redirecting a request that might carry credentials, check Section 11.19 🔥
NOTE: '' line wrapping per RFC 8792
https://client.example.net/return/123455\
?hash=x-gguKWTj8rQf7d7i3w3UhzvuJ5bpOlKyAlVpLxBffY\
&interact_ref=4IFWWIKYBC2PQ6U56NL1
the client instance MUST be able to process a request on that URI 👑. if the URI is HTTP, the request MUST be an HTTP GET 🎀.
when the request arrives, the client instance MUST parse the query parameters to pull out the hash and interaction reference values 🧚. then it MUST calculate and validate the hash, as described in Section 4.2.3 🔍. if the hash validates, the client instance sends a continuation request to the AS (see Section 5.1) using the interaction reference it just got 🦋. but if the hash does NOT validate, the client instance MUST NOT send the interaction reference to the AS 🙅♀️💀. no sending it along, periodt!
4.2.2. Completing Interaction with a Direct HTTP Request Callback
okay this one's the push finish method (from Sections 2.5.2.1 and 3.3.5) 💅. instead of sending the RO anywhere, the AS just makes an HTTP POST request straight to the client instance's callback URI 📬 to say "interaction's done, come get it!"
the HTTP message content is a JSON object with exactly two fields 🍓:
hash (string): the interaction hash value from Section 4.2.3. REQUIRED. 💖
interact_ref (string): the interaction reference generated for this interaction. REQUIRED. 💖
here's what that looks like on the wire 👀:
POST /push/554321 HTTP/1.1
Host: client.example.net
Content-Type: application/json
{
"hash": "pjdHcrti02HLCwGU3qhUZ3wZXt8IjrV_BtE3oUyOuKNk",
"interact_ref": "4IFWWIKYBC2PQ6U56NL1"
}
⚠️ okay this part is a big deal. the AS is making an outbound connection to a URI that an outside party (the client instance) handed it, so the AS MUST protect itself against Server-Side Request Forgery (SSRF) attacks when it makes this call 🛡️. Section 11.34 has the full tea on that 🫶.
SSRF is seriously not a vibe. treat that callback URI like a stranger at the door 🚪💀
when the request comes in, the client instance MUST parse the JSON object and validate the hash value as Section 4.2.3 describes 🧐. if either of those fails, the client instance MUST return an unknown_interaction error (Section 3.6) 🙈. if the hash validates, the client instance sends a continuation request to the AS as described in Section 5.1, using the interaction reference it received here ✨.
4.2.3. Calculating the Interaction Hash
okay this is the hash bit and it is SO clever 🧠💜. the "hash" parameter in the request to the client instance's callback URI ties the front-channel response to an ongoing request, using values that only the parties involved know 🔐. this is how the client instance protects itself from several kinds of session fixation and injection attacks (more on that in Section 11.25) 🛡️✨.
the AS MUST always provide this hash 💖, and the client instance MUST validate it when it's received 🔍.
so to calculate the "hash" value, whoever's doing the math builds a hash base string by mashing these together, in this exact order, with a single newline (0x0A) character between each one 🌈:
- the "nonce" value the client instance sent in the interaction finish field of the initial request (Section 2.5.2) 🎀
- the AS's nonce value from the interaction finish response (Section 3.3.5) 🌸
- the "interact_ref" the AS returned as part of the interaction finish method (Section 4.2) 🦋
- the grant endpoint URI the client instance used to make its initial request (Section 2) 🧚
no padding, no whitespace before or after any of the lines, and no trailing newline at the end 🙅♀️. here's a non-normative example of a hash base string built from those four elements 👀:
VJLO6A4CATR0KRO
MBDOFXG4Y5CVJCX821LH
4IFWWIKYB2PQ6U56NL1
https://server.example.com/tx
then the party hashes the bytes of the ASCII encoding of that string, using whatever algorithm the "hash_method" parameter under the "finish" key of the interaction finish request names (Section 2.5.2) 🔥. the result gets encoded with URL-Safe base64 with no padding [RFC4648], and that encoded string is your hash value 💅.
if "hash_method" is provided, its value MUST be one of the hash name strings in the IANA "Named Information Hash Algorithm Registry" [HASH-ALG] 📚. if "hash_method" isn't in the client instance's request at all, the algorithm defaults to "sha-256" 🌸.
for example, the "sha-256" method hashes the input string with the 256-bit SHA2 algorithm 🧁. here's the encoded "sha-256" hash of the hash base string from above 👇:
x-gguKWTj8rQf7d7i3w3UhzvuJ5bpOlKyAlVpLxBffY
and here's another one: the "sha3-512" method hashes the input string with the 512-bit SHA3 algorithm 🌈. here's the encoded "sha3-512" hash of that same example base string:
NOTE: '' line wrapping per RFC 8792
pyUkVJSmpqSJMaDYsk5G8WCvgY91l-agUPe1wgn-cc5rUtN69gPI2-S_s-Eswed8iB4\
PJ_a5Hg6DNi7qGgKwSQ
5. Continuing a Grant Request 🎀
okay so bestie, this is where things get SO good ✨ the AS can totally hand back an approved grant response (Section 3) right away, with everything the client instance asked for (access tokens from Section 3.2 and subject info from Section 3.4) 💅 but honestly? it's more common for the AS to put the grant request into the _pending_ state and then chat with the client instance a bunch of times over the grant's whole lifetime 🦋 a lot of that is about facilitating interaction (Section 4), but it can also be used to keep negotiating the original grant request (Section 2) by modifying it 🌸
being able to keep going with a request that already started is iconic, no cap 👑 the client instance can present extra info from an interaction, tweak the initial request, and even revoke a grant request that's still in progress 🙈
so to make this ongoing back-and-forth work, the AS gives the client software a continuation API 🧚 it returns a continue field in the response (Section 3.1) with everything the client instance needs to reach that API, including a URI to hit and a special access token to use during those requests, called the "continuation access token" 💖
all requests to the continuation API are protected by a bound continuation access token 🔥 that token is bound to the same key and method the client instance used for the initial request (or its most recent rotation) 🫶 which means whenever the client instance calls the continuation URI, it MUST present the continuation access token as described in Section 7.2 AND prove it has the client instance's key (or its most recent rotation) by signing the request as described in Section 7.3 😭 the AS MUST validate that signature and make sure it's bound to the right key for that continuation access token 💅
access tokens other than continuation access tokens MUST NOT be usable for continuation requests 💀 and the other way around: continuation access tokens MUST NOT be usable to make authorized requests to RSs, even if they're co-located within the AS 🙅♀️
here's a non-normative example of the client instance making a POST to a unique URI and signing it with HTTP message signatures ✨
POST /continue/KSKUOMUKM HTTP/1.1
Authorization: GNAP 80UPRY5NM33OMUKMKSKU
Host: server.example.com
Content-Length: 0
Signature-Input: sig1=...
Signature: sig1=...
the AS MUST be able to tell from the request exactly which ongoing grant request is being accessed, using the combo of the continuation URI and the continuation access token 🦋 if the AS can't pin down a single active grant request to map the continuation request to, it MUST return an invalid_continuation error (Section 3.6) 😭
here's another non-normative example, this time a POST to a stable continuation endpoint with the interaction reference (Section 5.1) 🌈 the client instance includes the access token and signs with HTTP message signatures 💜
POST /continue HTTP/1.1
Host: server.example.com
Content-Type: application/json
Authorization: GNAP 80UPRY5NM33OMUKMKSKU
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
{
"interact_ref": "4IFWWIKYBC2PQ6U56NL1"
}
and in this non-normative alternative, the client instance was given a continuation URI that's unique to this ongoing grant request 🍓
POST /tx/rxgIIEVMBV-BQUO7kxbsp HTTP/1.1
Host: server.example.com
Content-Type: application/json
Authorization: GNAP eyJhbGciOiJub25lIiwidHlwIjoiYmFkIn0
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
{
"interact_ref": "4IFWWIKYBC2PQ6U56NL1"
}
in both cases the AS works out which grant is being asked for based on the URI and the continuation access token ✨
if a wait parameter came back in the continuation response (Section 3.1), the client instance MUST NOT call the continuation URI before waiting that many seconds 🧚 and if there's no wait period, the client instance MUST NOT poll immediately and SHOULD wait at least 5 seconds 🌸 if the client instance ignores the wait period, the AS MUST return the too_fast error (Section 3.6) 🙈
the response from the AS is a JSON object of a grant response, and it MAY contain any of the fields from Section 3, which the subsections below go into more detail on 💖
if the AS decides the client instance can keep making continuation requests, it MUST include a new continuation response (Section 3.1) 🔥 that new one MUST include a continuation access token too, and this token SHOULD be a new access token, which invalidates the previous one 🫶 if the AS doesn't return a new continuation response, the client instance MUST NOT make another continuation request 😭 and if it does anyway, the AS MUST return an invalid_continuation error (Section 3.6) 💀
for any continuation functions that need the client instance to send message content, that content MUST be a JSON object 🎀
for all requests to the grant continuation API, the AS MAY use long polling mechanisms like the ones discussed in [RFC6202] 🌈 so instead of replying right away with the current status, the AS can take extra time to process and fulfill the request before sending the HTTP response 🦋 for example, if the AS gets a continuation request while the grant request is in the _processing_ state, it could wait until the grant request moves to _pending_ or _approved_ before responding 💅
5.1. Continuing after a Completed Interaction
when the AS responds to the client instance's finish method (as in Section 4.2.1), that response includes an interaction reference 🍓 the client instance MUST include that value as the field interact_ref in a POST request to the continuation URI ✨
POST /continue HTTP/1.1
Host: server.example.com
Content-Type: application/json
Authorization: GNAP 80UPRY5NM33OMUKMKSKU
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
{
"interact_ref": "4IFWWIKYBC2PQ6U56NL1"
}
the interaction reference is a one-time-use value (see Section 4.2.1), so if the client instance needs more continuation calls after this one, it MUST NOT include the interaction reference in those later calls 🙅♀️ if the AS spots a client instance submitting an interaction reference when the request is NOT in the _pending_ state, the AS MUST return a too_many_attempts error (Section 3.6) and SHOULD invalidate the ongoing request by moving it to the _finalized_ state 💀
if the grant request is in the _approved_ state, the grant response (Section 3) MAY contain any newly created access tokens (Section 3.2) or newly released subject information (Section 3.4) 🎀 it MAY also contain a new continuation response (Section 3.1) like described above, but it SHOULD NOT contain any interaction responses (Section 3.3) 🌸
if the grant request is in the _pending_ state, the grant response (Section 3) MUST NOT contain access tokens or subject information 🙈 but it MAY contain a new interaction response (Section 3.3) for any interaction methods the AS hasn't exhausted yet 🦋
for example, if the request works and the AS issues access tokens and releases opaque subject claims, the response could look like this 🧚
NOTE: '' line wrapping per RFC 8792
{
"access_token": {
"value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
"manage": {
"uri": "https://server.example.com/token/PRY5NM33O",
"access_token": {
"value": "B8CDFONP21-4TB8N6.BW7ONM"
}
}
},
"subject": {
"sub_ids": [ {
"format": "opaque",
"id": "J2G8G8O4AZ"
} ]
}
}
with that example, the client instance can't make another continuation request because there's no continue field 😭
in this non-normative example, the RO denied the client instance's request, so the AS responds with this 💅
{
"error": "user_denied",
"continue": {
"access_token": {
"value": "33OMUKMKSKU80UPRY5NM"
},
"uri": "https://server.example.com/continue",
"wait": 30
}
}
in the example above, the AS includes the continue field, so the client instance can keep the grant negotiation going, maybe by modifying the request like we talk about in Section 5.3 🫶
5.2. Continuing during Pending Interaction (Polling) 🔁
okay so, if the client instance doesn't send a finish parameter 🥺, it's gonna need to keep checking in with the AS 🔍 until the RO has authorized the request. To do that, it sends a POST to the continuation URI like in Section 5.1, but this time there's no message content 💅. It's literally just a poll 💖:
POST /continue HTTP/1.1
Host: server.example.com
Authorization: GNAP 80UPRY5NM33OMUKMKSKU
Signature-Input: sig1=...
Signature: sig1=...
If the grant request is in the approved state ✨, the grant response (Section 3) MAY contain any brand new access tokens (Section 3.2) or freshly released subject claims (Section 3.4). The response MAY also include a new continuation response (Section 3.1), same as we talked about above. If there's a continue field, it SHOULD include a wait field 🕰️ so the client instance can poll at a reasonable pace 🐢. And the response SHOULD NOT contain interaction responses (Section 3.3), because the RO is already done 🙅♀️.
If the grant request is in the pending state 🥹, the grant response (Section 3) MUST NOT contain access tokens or subject information. It MAY contain a new interaction response (Section 3.3) for any interaction methods that the AS hasn't used up yet 🦋.
For example, if the RO hasn't authorized the request yet, the AS could tell the client instance to try a continuation request again later 🧚. In this non-normative example, a brand new access token has been issued for the call, and the client instance will use it in its next continuation request 🎀:
{
"continue": {
"access_token": {
"value": "33OMUKMKSKU80UPRY5NM"
},
"uri": "https://server.example.com/continue",
"wait": 30
}
}
If the request works and the AS issues access tokens and releases subject information, the response could look something like this non-normative example 🌈. (Heads up, the NOTE below is part of the spec and it's about line wrapping 🤓.)
NOTE: '' line wrapping per RFC 8792
{
"access_token": {
"value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
"manage": {
"uri": "https://server.example.com/token/PRY5NM33O",
"access_token": {
"value": "B8CDFONP21-4TB8N6.BW7ONM"
}
}
},
"subject": {
"sub_ids": [ {
"format": "opaque",
"id": "J2G8G8O4AZ"
} ]
}
}
See Section 11.23 for considerations on polling for continuation without an interaction finish method. This one is serious, bestie 💀, so go read it.
In error conditions, the AS responds to the client instance with an error code, as discussed in Section 3.6 🚨. For example, if the client instance has polled too many times before the RO approves the request, the AS would respond with something like this 🙃:
{
"error": "too_many_attempts"
}
Since this response doesn't include a continue field, the client instance can't keep polling the AS for updates, and the grant request is now finalized 🪦. If the client instance still needs access to the resource, it'll have to start over with a new grant request 💜.
5.3. Modifying an Existing Request ✨
okay so sometimes the client instance needs to change an ongoing request, whether or not tokens have already been issued or subject info has already been released 💅 to do that, it sends an HTTP PATCH request to the continuation URI and includes whatever fields it wants to change. anything it leaves out just stays the same as the original request 🎀
a grant request tied to a modification request MUST be in the approved or pending state 🦋 once the AS gets a valid modification request, it MUST move the grant request into the processing state and re-evaluate authorization in the new context from the update, because the scope and context of the request could have shifted 👑
the client instance MAY include the access_token and subject fields as described in Sections 2.1 and 2.2 🌸 putting these in overrides whatever was in the initial request, and that MAY set off extra requirements and policies on the AS side 💀 for example, if the client wants more access, the AS could ask the RO for more consent through interaction 🙈 if the client wants less access, the AS could decide the authorization it already has is enough and hand back the smaller set of rights right away ✨ and if the grant request was previously approved, the AS could choose to remember the bigger set of access rights, so the client can later ask for different subsets of what was granted 🧚 the details of all this processing are out of scope for this specification, but one possible approach looks like this:
- A client instance requests access to Foo, and the RO grants it. This results in an access token: AT1.
- The client instance later modifies the grant request to include Foo and Bar together. Since the client instance was previously granted Foo under this grant request, the RO is prompted to allow the client instance access to Foo and Bar together. This results in a new access token: AT2. This access token has access to both Foo and Bar. The rights of the original access token AT1 are not modified.
- The client instance makes another grant modification to ask only for Bar. Since the client instance was previously granted Foo and Bar together under this grant request, the RO is not prompted, and the access to Bar is granted in a new access token: AT3. This new access token does not allow access to Foo.
- The original access token AT1 expires, and the client seeks a new access token to replace it. The client instance makes another grant modification to ask only for Foo. Since the client instance was previously granted Foo and Bar together under this grant request, the RO is not prompted, and the access to Foo is granted in a new access token: AT4. This new access token does not allow access to Bar.
all four access tokens are independent of each other but still tied to the same underlying grant request 🍓 each of them could also be rotated through token management, if that's available 🫶 for instance, instead of asking for a brand new token to replace AT1, the client instance could ask to refresh AT1 using the rotation method of the token management API 🌈 that gives you a refreshed AT1 with a different token value and expiration than the original, but it still only allows access to Foo, same as before ✨
the client instance MAY include the interact field as described in Section 2.5 💖 including it tells the AS that the client can drive interaction with the end user, and it replaces any values from a previous request 🌟 the AS MAY respond to any of the interaction responses described in Section 3.3, just like it would for a brand new request 🦋
the client instance MAY include the user field as described in Section 2.4 to present new assertions or info about the end user 🎀 the AS SHOULD check that this user info lines up with any user info the client already presented or that's otherwise associated with this grant request 👀
the client instance MUST NOT include the client field of the request, since the client is assumed not to have changed 🙅♀️ changing client instance info, including rotating the keys associated with the client instance, is outside the scope of this specification 🔒
the client instance MUST NOT include post-interaction responses like the ones described in Section 5.1 😭
modification requests MUST NOT alter previously issued access tokens 🚫 instead, any access tokens issued from a continuation count as new, separate access tokens 💜 the AS MAY revoke previously issued access tokens after a modification has happened 🥀
if the AS can grant the modified request immediately (the grant request is in the approved state), the grant response (Section 3) MAY contain any newly created access tokens (Section 3.2) or newly released subject claims (Section 3.4) 🎉 the response MAY also contain a new continuation response (Section 3.1) as described above 🧚 and if interaction can happen, the response SHOULD contain interaction responses (Section 3.3) too 🌸
here's an example where a client initially requests a set of resources using references:
POST /tx HTTP/1.1
Host: server.example.com
Content-Type: application/json
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
{
"access_token": {
"access": [
"read", "write"
]
},
"interact": {
"start": ["redirect"],
"finish": {
"method": "redirect",
"uri": "https://client.example.net/return/123455",
"nonce": "LKLTI25DK82FX4T4QFZC"
}
},
"client": "987YHGRT56789IOLK"
}
the RO grants access and the AS issues a token 👑 in its final response, the AS includes a continue field, which has a separate access token for hitting the continuation API:
{
"continue": {
"access_token": {
"value": "80UPRY5NM33OMUKMKSKU"
},
"uri": "https://server.example.com/continue",
"wait": 30
},
"access_token": {
"value": "RP1LT0-OS9M2P_R64TB",
"access": [
"read", "write"
]
}
}
this continue field lets the client instance make a continuation call later on ✨ some time down the line, the client realizes it doesn't need "write" access anymore 🙃 so it modifies the ongoing request and asks for just "read" instead of both "read" and "write" like before 💅
PATCH /continue HTTP/1.1
Host: server.example.com
Content-Type: application/json
Authorization: GNAP 80UPRY5NM33OMUKMKSKU
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
{
"access_token": {
"access": [
"read"
]
}
...
}
the AS replaces the access from the first request, which lets it figure out whether any consent that was already given still applies 🧠 here, the AS would see that narrowing the access means new access tokens can go out to the client without any extra interaction or consent 🌷 the AS would likely revoke the previously issued access tokens that had the broader access rights, unless they were issued with the durable flag 🥀
{
"continue": {
"access_token": {
"value": "M33OMUK80UPRY5NMKSKU"
},
"uri": "https://server.example.com/continue",
"wait": 30
},
"access_token": {
"value": "0EVKC7-2ZKwZM_6N760",
"access": [
"read"
]
}
}
now for another example, where the client starts with read-only access and later needs to step up 🦋 the initial request could look like this HTTP message:
POST /tx HTTP/1.1
Host: server.example.com
Content-Type: application/json
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
{
"access_token": {
"access": [
"read"
]
},
"interact": {
"start": ["redirect"],
"finish": {
"method": "redirect",
"uri": "https://client.example.net/return/123455",
"nonce": "LKLTI25DK82FX4T4QFZC"
}
},
"client": "987YHGRT56789IOLK"
}
the RO grants access and the AS issues a token 🎀 in its final response, the AS includes a continue field:
{
"continue": {
"access_token": {
"value": "80UPRY5NM33OMUKMKSKU"
},
"uri": "https://server.example.com/continue",
"wait": 30
},
"access_token": {
"value": "RP1LT0-OS9M2P_R64TB",
"access": [
"read"
]
}
}
this lets the client instance make an eventual continuation call 🌈 later on, the client realizes it now needs "write" access in addition to "read" 😤 because this expands what it asked for before, the client also includes a new interaction field, in case the AS needs to talk to the RO again to get more authorization 🤝 notice that the client instance's nonce and callback are different from the initial request 👀 the original callback was already used in the first exchange, and callbacks are meant for one-time use, so a new one has to be included to use the callback again 🔁
PATCH /continue HTTP/1.1
Host: server.example.com
Content-Type: application/json
Authorization: GNAP 80UPRY5NM33OMUKMKSKU
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
{
"access_token": {
"access": [
"read", "write"
]
},
"interact": {
"start": ["redirect"],
"finish": {
"method": "redirect",
"uri": "https://client.example.net/return/654321",
"nonce": "K82FX4T4LKLTI25DQFZC"
}
}
}
from here, the AS can tell the client is asking for more than it was granted before 🧐 but since the client has also given a way to interact with the RO, the AS can use that to gather the extra consent ✨ the protocol carries on just like it would for a new request 💜 since the old access tokens only cover a subset of the rights requested here, the AS might decide not to revoke them 🌸 still, any access tokens granted after this update are brand new and don't change the rights of existing access tokens 🫶
5.4. Revoking a Grant Request 🛑
okay so if the client instance wants to cancel an ongoing grant request and toss it into the finalized state 💅, it makes an HTTP DELETE request to the continuation URI.
DELETE /continue HTTP/1.1
Host: server.example.com
Content-Type: application/json
Authorization: GNAP 80UPRY5NM33OMUKMKSKU
Signature-Input: sig1=...
Signature: sig1=...
if the request gets revoked successfully, the AS responds with HTTP status code 204 (No Content) 🫶. The AS SHOULD revoke all associated access tokens, if possible. The AS SHOULD also disable all token rotation and other token management functions on those access tokens, if possible. Once the grant request is in the finalized state, it MUST NOT be moved to any other state. no takebacks, bestie 🔒
If the request doesn't get revoked, the AS responds with an invalid_continuation error (Section 3.6) 🥲
6. Token Management 🧚
if an access token response includes the manage field (see Section 3.2.1), the client instance MAY call this URI to manage the access token with the rotate and revoke actions defined in the following subsections 🦋 Other actions are undefined by this specification, so don't go inventing your own 🙈
{
"access_token": {
"value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
"flags": ["bearer"],
"manage": {
"uri": "https://server.example.com/token/PRY5NM33O",
"access_token": {
"value": "B8CDFONP21-4TB8N6.BW7ONM"
}
}
}
}
The token management access token issued under the manage field is what protects every call to the token management API 🔐 The client instance MUST present proof of the key associated with the token, along with the value of the token management access token. no skipping this part!!
The AS MUST validate the proof and make sure it's associated with the token management access token ✅
The AS MUST uniquely identify the token being managed from the token management URI, the token management access token, or a combination of both. it's giving detective energy 🕵️♀️
6.1. Rotating the Access Token Value 🔄
if the client instance has an access token and that token expires, the client instance might want to rotate it to a new value without an expiration date 🥹 Rotating an access token means issuing a new access token in place of the existing one, with the same rights and properties as the original, except for an updated token value and expiration time. fresh start, same vibes ✨
To rotate an access token, the client instance makes an HTTP POST to the token management URI with no message content. It sends the access token in the authorization header (see Section 7.2) and signs the request with the appropriate key 🖊️
POST /token/PRY5NM33O HTTP/1.1
Host: server.example.com
Authorization: GNAP B8CDFONP21-4TB8N6.BW7ONM
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
The client instance cannot ask to change the access rights attached to the access token during a rotation request 🙅♀️ If it wants an access token with different access rights for this grant request, it has to call the continuation API's update functionality (Section 5.3) to get a new access token. It can also create a brand new grant request with the rights it needs 🌸
The AS checks that the token management access token presented is associated with the management URI, that the AS issued the token to the given client instance, and that the presented key is the correct key for the token management access token. The AS works out which access token is being rotated from the token management URI, the token management access token, or both 🔍
If the token is validated and the key is appropriate for the request, the AS MUST invalidate the current access token value associated with this URI, if possible. Note that stateless access tokens can make proactive revocation difficult within a system; see Section 11.32 👀
For successful rotations, the AS responds with an HTTP status code 200 (OK) and JSON-formatted message content containing the rotated access token in the access_token field described in Section 3.2.1 🎉 The value of the access token MUST NOT be the same as the current value of the access token used to access the management API. The response MUST include an access token management URI, and the value of this URI MAY be different from the URI the client instance used to make the rotation call. The client instance MUST use this new URI to manage the rotated access token. update your bookmarks, bestie 📌
The access rights in the access array for the rotated access token MUST be included in the response, and they MUST be the same as the token had before rotation 💜
NOTE: '' line wrapping per RFC 8792
{
"access_token": {
"value": "FP6A8H6HY37MH13CK76LBZ6Y1UADG6VEUPEER5H2",
"manage": {
"uri": "https://server.example.com/token/PRY5NM33O",
"access_token": {
"value": "B8CDFONP21-4TB8N6.BW7ONM"
}
},
"expires_in": 3600,
"access": [
{
"type": "photo-api",
"actions": [
"read",
"write",
"dolphin"
],
"locations": [
"https://server.example.net/",
"https://resource.local/other"
],
"datatypes": [
"metadata",
"images"
]
},
"read", "dolphin-metadata"
]
}
}
If the AS is unable or unwilling to rotate the value of the access token, it responds with an invalid_rotation error (Section 3.6) 😬 When the client instance gets that error, it MUST consider the access token to not have changed its state. the token is still the same girl 🫠
6.1.1. Binding a New Key to the Rotated Access Token 🔑
if the client instance wants to bind a new presentation key to an access token, it MUST present both the new key and the proof of the previous key material in the access token rotation request 🫶 The client instance makes an HTTP POST as a JSON object with the following field:
key: The new key value or reference in the format described in Section 7.1. Note that keys passed by value are always public keys. REQUIRED when doing key rotation.
The proofing method and parameters for the new key MUST be the same as those established for the previous key. no sneaky swaps 🚫
The client instance MUST prove possession of both the currently bound key and the newly requested key simultaneously in the rotation request. Specifically, the signature from the previous key MUST cover the value or reference of the new key, and the signature of the new key MUST cover the signature value of the old key. Exactly how this gets done depends on the proofing method in use. For example, the HTTP message signatures proofing method uses multiple signatures in the request, as described in Section 7.3.1.1. Here's what that looks like 👇
POST /token/PRY5NM33O HTTP/1.1
Host: server.example.com
Authorization: GNAP B8CDFONP21-4TB8N6.BW7ONM
Signature-Input: \
sig1=("@method" "@target-uri" "content-digest" \
"authorization"),\
sig2=("@method" "@target-uri" "content-digest" \
"authorization" "signature";key="sig1" \
"signature-input";key="sig1")
Signature: sig1=..., sig2=...
Content-Digest: sha-256=...
{
"key": {
"proof": "httpsig",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "xyz-2",
"alg": "RS256",
"n": "kOB5rR4Jv0GMeLaY6_It_r3ORwdf8ci_JtffXyaSx8xY..."
}
}
}
Failing to present the appropriate proof of either the new key or the previous key for the access token, as defined by the proofing method, MUST result in an invalid_rotation error code from the AS (Section 3.6). rip to that rotation 💀
An attempt to change the proofing method or parameters, including an attempt to rotate the key of a bearer token (which has no key), MUST result in an invalid_rotation error code returned from the AS (Section 3.6). bearer tokens don't have keys, so no key drama for them 🙈
If the AS doesn't allow rotation of the access token's key for any reason, including but not limited to lack of permission for this client instance or lack of capability by the AS, the AS MUST return a key_rotation_not_supported error code (Section 3.6). not everyone gets to play with keys, and that's okay 🌈
6.2. Revoking the Access Token 🗑️
if the client instance wants to revoke the access token proactively, like when a user says they no longer want it to have access or the app notices it's being uninstalled, it can use the token management URI to tell the AS that the AS SHOULD invalidate the access token for all purposes 🦋
The client instance makes an HTTP DELETE request to the token management URI, presenting the access token and signing the request with the appropriate key.
DELETE /token/PRY5NM33O HTTP/1.1
Host: server.example.com
Authorization: GNAP B8CDFONP21-4TB8N6.BW7ONM
Signature-Input: sig1=...
Signature: sig1=...
If the key presented is associated with the token (or with the client instance, in the case of a bearer token), the AS MUST invalidate the access token, if possible, and return an HTTP response code 204 ✨
204 No Content
the AS MAY revoke an access token at any time for any reason, but the token management function is specifically for the client instance's use 💅 If the access token has already expired or was already revoked some other way, the AS SHOULD honor the revocation request to the token management URI as valid, since the end result is the same: the token still isn't usable. zero drama 🫶
7. Securing Requests from the Client Instance 🔐
in GNAP, the client instance secures its requests to an AS and RS by presenting an access token, proof of a key it possesses (aka, a "key proof"), or both an access token and key proof together 🎀
- When an access token is used with a key proof, this is a bound token request. This type of request is used for calls to the RS as well as the AS during grant negotiation.
- When a key proof is used with no access token, this is a non-authorized signed request. This type of request is used for calls to the AS to initiate a grant negotiation.
- When an access token is used with no key proof, this is a bearer token request. This type of request is used only for calls to the RS and only with access tokens that are not bound to any key, as described in Section 3.2.1.
- When neither an access token nor key proof is used, this is an unsecured request. This type of request is used optionally for calls to the RS as part of an RS-first discovery process, as described in Section 9.1. 🌸
7.1. Key Formats ✨
Okay so GNAP needs key material in a bunch of places, and it can show up by value or by reference 🔑 Key material sent by value is a JSON object with a few fields, and we're about to go through all of them, bestie 🎀
Every key is tied to a specific key proofing method, and that method gets flagged in the proof field of the key object 💅
proof(string or object): The form of proof that the client instance will use when presenting the key. The valid values of this field and the processing requirements for each are detailed in Section 7.3. REQUIRED.
A key presented by value MUST be a public key 🔐 and it MUST show up in only one supported format, as discussed in Section 11.35. Heads up: most formats carry the full public key, but some only carry a value that's cryptographically derived from it. Peep Section 11.7 for more on how public keys get presented 💜
jwk(object): The public key and its properties represented as a JSON Web Key (JWK) [RFC7517]. A JWK MUST contain thealg(Algorithm) andkid(Key ID) parameters. Thealgparameter MUST NOT be "none". Thex5c(X.509 Certificate Chain) parameter MAY be used to provide the X.509 representation of the provided public key. OPTIONAL.cert(string): The Privacy-Enhanced Mail (PEM) serialized value of the certificate used to sign the request, with optional internal whitespace per [RFC7468]. The PEM header and footer are optionally removed. OPTIONAL.cert#S256(string): The certificate thumbprint calculated as per MTLS for OAuth [RFC8705] in base64url encoding. Note that this format does not include the full public key. OPTIONAL.
Additional key formats can be defined in the "GNAP Key Formats" registry (Section 10.17) 🌈
Here's a non-normative example (vibes only, not binding 🧚) showing one key in two different formats. The key is meant for the HTTP message signatures proofing mechanism (Section 7.3.1), and you can tell because of the httpsig value in the proof field.
As a JWK:
"key": {
"proof": "httpsig",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "xyz-1",
"alg": "RS256",
"n": "kOB5rR4Jv0GMeLaY6_It_r3ORwdf8ci_JtffXyaSx8xY..."
}
}
As a certificate in PEM format:
"key": {
"proof": "httpsig",
"cert": "MIIEHDCCAwSgAwIBAgIBATANBgkqhkiG9w0BAQsFA..."
}
When a key shows up in GNAP, proof of that key material MUST be used to bind the request 🔗 What that looks like depends on where in the protocol the key is being used. For a key in a client instance's initial request (Section 2.3), the key value represents the client instance's public key, and proof of that key MUST be presented in that request. For a key in an access token response (Section 3.2.1), the proof of that key MUST be used when the client instance later presents the access token to the RS. ✨
7.1.1. Key References
Keys in GNAP can also be passed by reference, so the party receiving the reference can work out the right keying material for that part of the protocol 🧩 A key reference is a single opaque string:
"key": "S-P4XJQ_RYJCRTSU1.63N3E"
Keys referenced this way MAY be shared symmetric keys. Check out the additional considerations for symmetric keys in Section 11.7 🔐 The key reference MUST NOT contain any unencrypted private or shared symmetric key information. Whoa, no leaking secrets in there 🙈
Keys referenced in this manner MUST be bound to a single proofing mechanism.
The means of dereferencing this reference to a key value and proofing mechanism are out of scope for this specification. Commonly, the AS creates key references, and the client doesn't necessarily need to understand them. These are an internal reference to the AS, like an identifier for a record in a database. In other apps, it can be useful to have key references that both clients and the AS can resolve, which could be done by a client publishing a public key at a URI, for example. For interoperability, this method could later be described as an extension, but doing that is out of scope for this specification 🦋
7.1.2. Key Protection
The security of GNAP relies on the cryptographic security of the keys themselves. Real talk, this part is serious 😭 When symmetric keys are used in GNAP, a key management system or secure key derivation mechanism MUST be used to supply the keys. Symmetric keys MUST NOT be a human-memorable password or a value derived from one. Symmetric keys MUST NOT be passed by value from the client instance to the AS. Not even a little 💀
Additional security considerations apply when rotating keys (see Section 11.22) 🔄
7.2. Presenting Access Tokens
Access tokens get issued to client instances so they can make an authorized call to an API 🎟️ How the client instance sends the token depends on whether the token is bound to a key and, if it is, which proofing method goes with that key. That info comes from the key parameter and the bearer flag in the access token response structure (Section 3.2.1) 👑
If the flags field does not contain the bearer flag and the key is absent, the access token MUST be sent using the same key and proofing mechanism that the client instance used in its initial request (or its most recent rotation).
If the flags field does not contain the bearer flag and the key value is an object as described in Section 7.1, the access token MUST be sent using the key and proofing mechanism defined by the value of the proof field within the key object.
The access token MUST be sent using the HTTP Authorization request header field and the "GNAP" authorization scheme, along with a key proof as described in Section 7.3 for the key bound to the access token. For example, an access token bound using HTTP message signatures would be sent like this 💖
NOTE: '\' line wrapping per RFC 8792
GET /stuff HTTP/1.1
Host: resource.example.com
Authorization: GNAP 80UPRY5NM33OMUKMKSKU
Signature-Input: sig1=("@method" "@target-uri" "authorization")\
;created=1618884473;keyid="gnap-rsa";nonce="NAOEJF12ER2";tag="gnap"
Signature: sig1=:FQ+EjWqc38uLFByKa5y+c4WyYYwCTGUhidWKfr5L1Cha8FiPEw\
DxG7nWttpBLS/B6VLfkZJogPbclySs9MDIsAIJwHnzlcJjwXWR2lfvm2z3X7EkJHm\
Zp4SmyKOS34luAiKR1xwf32NYFolHmZf/SbHZJuWvQuS4U33C+BbsXz8MflFH1Dht\
H/C1E5i244gSbdLCPxzABc/Q0NHVSLo1qaouYIvnxXB8OT3K7mwWjsLh1GC5vFThb\
3XQ363r6f0OPRa4qWHhubR/d/J/lNOjbBdjq9AJ69oqNJ+A2XT+ZCrVasEJE0OBvD\
auQoiywhb8BMB7+PEINsPk5/8UvaNxbw==:
If the flags field contains the bearer flag, the access token is a bearer token, and it MUST be sent using the Authorization request header field method defined in [RFC6750]. Bearer tokens are giving simple and straightforward 🍓
Authorization: Bearer OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0
The Form-Encoded Body Parameter and URI Query Parameter methods of [RFC6750] MUST NOT be used for GNAP access tokens. Absolutely not, those are off the table 🚫💅
7.3. Proving Possession of a Key with a Request ✨
okay so bestie, this is where we make sure whoever shows up with a key actually owns it 🔐💖 every key the client instance hands to the AS or RS MUST be validated as part of the very request it shows up in 🫶 the type of binding is picked by the proof parameter of the key object from Section 7.1 🎀 and proofing methods come in two flavors: a plain string with just the method name, or a JSON object with a required field called method 💅
method: the name of the key proofing method being used. REQUIRED. 🌸
individual methods written as objects MAY define additional parameters as members of that object 🦋
here are the values this spec defines (it's giving a whole menu 🍓):
"httpsig"(string or object): HTTP message signing 🖋️ see Section 7.3.1."mtls"(string): MTLS certificate verification 🔒 see Section 7.3.2."jwsd"(string): a detached JWS signature header 🧾 see Section 7.3.3."jws"(string): attached JWS Payload 📦 see Section 7.3.4.
additional proofing methods can be defined in the "GNAP Key Proofing Methods" registry (Section 10.16) 🧚 so the list can grow! 🌈
proofing methods MAY be defined as both an object and a string 👑 for example, the httpsig method can be written as an object with its parameters spelled out, like this:
{
"proof": {
"method": "httpsig",
"alg": "ecdsa-p384-sha384",
"content-digest-alg": "sha-256"
}
}
the httpsig method also has default behavior when it's passed as a plain string 💅 in that case it uses the signature algorithm from the associated key material, and the content digest is calculated using sha-256 ✨ so this shortened form works:
{
"proof": "httpsig"
}
⚠️ all key binding methods used by this spec MUST cover all relevant portions of the request, including anything that would change the nature of the request 🙈 that means the URI being called, the HTTP method being used, any relevant HTTP headers and values, and the HTTP message content itself 💀 the verifier of the signed message MUST validate all components of the signed message so nothing has been tampered with or swapped in a way that would change what the request is 😭 and definitions of key binding methods MUST enumerate how these requirements are fulfilled 🔥
when a key proofing mechanism is bound to an access token, the key being presented MUST be the key associated with the access token, and the access token MUST be covered by the signature method of the proofing mechanism 💜 no sneaking a different key in there, periodt 🙅♀️
the key binding methods in this section MAY be used by other components making calls as part of GNAP, like the extensions letting the RS make calls to the AS defined in [GNAP-RS] 🌸 so to keep things general, "signer" and "verifier" are used as generic terms in the subsections below 🧚 in the core functions of GNAP in this document, the "signer" is the client instance, and the "verifier" is the AS (for grant requests) or RS (for resource requests), as appropriate 🫶
when it's used for delegation in GNAP, these key binding mechanisms let the AS make sure the keys the client instance presented in the initial request are the ones in control of whoever calls any follow-up or continuation requests 😤 to make that work, the continuation response (Section 3.1) includes an access token bound to the client instance's key (Section 2.3), and that key (or its most recent rotation) MUST be proved in all continuation requests (Section 5) 🦋 token management requests (Section 6) are similarly bound to either the access token's own key or, for bearer tokens, the client instance's key 🎀
in the following subsections, unless otherwise noted, the RS256 JSON Object Signing and Encryption (JOSE) signature algorithm (defined in Section 3.3 of [RFC7518]) is applied using the following RSA key (shown here in JWK format) 🔑
NOTE: '' line wrapping per RFC 8792
{
"kid": "gnap-rsa",
"p": "xS4-YbQ0SgrsmcA7xDzZKuVNxJe3pCYwdAe6efSy4hdDgF9-vhC5gjaRk\
i1wWuERSMW4Tv44l5HNrL-Bbj_nCJxr_HAOaesDiPn2PnywwEfg3Nv95Nn-\
eilhqXRaW-tJKEMjDHu_fmJBeemHNZI412gBnXdGzDVo22dvYoxd6GM",
"kty": "RSA",
"q": "rVdcT_uy-CD0GKVLGpEGRR7k4JO6Tktc8MEHkC6NIFXihk_6vAIOCzCD6\
LMovMinOYttpRndKoGTNdJfWlDFDScAs8C5n2y1STCQPRximBY-bw39-aZq\
JXMxOLyPjzuVgiTOCBIvLD6-8-mvFjXZk_eefD0at6mQ5qV3U1jZt88",
"d": "FHlhdTF0ozTliDxMBffT6aJVKZKmbbFJOVNten9c3lXKB3ux3NAb_D2dB\
7inp9EV23oWrDspFtvCvD9dZrXgRKMHofkEpo_SSvBZfgtH-OTkbY_TqtPF\
FLPKAw0JX5cFPnn4Q2xE4n-dQ7tpRCKl59vZLHBrHShr90zqzFp0AKXU5fj\
b1gC9LPwsFA2Fd7KXmI1drQQEVq9R-o18Pnn4BGQNQNjO_VkcJTiBmEIVT_\
KJRPdpVJAmbgnYWafL_hAfeb_dK8p85yurEVF8nCK5oO3EPrqB7IL4UqaEn\
5Sl3u0j8x5or-xrrAoNz-gdOv7ONfZY6NFoa-3f8q9wBAHUuQ",
"e": "AQAB",
"qi": "ogpNEkDKg22Rj9cDV_-PJBZaXMk66Fp557RT1tafIuqJRHEufSOYnsto\
bWPJ0gHxv1gVJw3gm-zYvV-wTMNgr2wVsBSezSJjPSjxWZtmT2z68W1DuvK\
kZy15vz7Jd85hmDlriGcXNCoFEUsGLWkpHH9RwPIzguUHWmTt8y0oXyI",
"dp": "dvCKGI2G7RLh3WyjoJ_Dr6hZ3LhXweB3YcY3qdD9BnxZ71mrLiMQg4c_\
EBnwqCETN_5sStn2cRc2JXnvLP3G8t7IFKHTT_i_TSTacJ7uT04MSa053Y3\
RfwbvLjRNPR0UKAE3ZxROUoIaVNuU_6-QMf8-2ilUv2GIOrCN87gP_Vk",
"alg": "RS256",
"dq": "iMZmELaKgT9_W_MRT-UfDWtTLeFjIGRW8aFeVmZk9R7Pnyt8rNzyN-IQ\
M40ql8u8J6vc2GmQGfokLlPQ6XLSCY68_xkTXrhoU1f-eDntkhP7L6XawSK\
Onv5F2H7wyBQ75HUmHTg8AK2B_vRlMyFKjXbVlzKf4kvqChSGEz4IjQ",
"n": "hYOJ-XOKISdMMShn_G4W9m20mT0VWtQBsmBBkI2cmRt4Ai8BfYdHsFzAt\
YKOjpBR1RpKpJmVKxIGNy0g6Z3ad2XYsh8KowlyVy8IkZ8NMwSrcUIBZGYX\
jHpwjzvfGvXH_5KJlnR3_uRUp4Z4Ujk2bCaKegDn11V2vxE41hqaPUnhRZx\
e0jRETddzsE3mu1SK8dTCROjwUl14mUNo8iTrTm4n0qDadz8BkPo-uv4BC0\
bunS0K3bA_3UgVp7zBlQFoFnLTO2uWp_muLEWGl67gBq9MO3brKXfGhi3kO\
zywzwPTuq-cVQDyEN7aL0SxCb3Hc4IdqDaMg8qHUyObpPitDQ"
}
key proofing methods SHOULD define a mechanism to allow the rotation of keys discussed in Section 6.1.1 🔄 key rotation mechanisms MUST define a way for presenting proof of two keys at the same time, with the following attributes ✨
- the value of or reference to the new key material MUST be signed by the existing key 🖊️ generally speaking, this means using the existing key to sign the content of the message that contains the new key 💖
- the signature of the old key MUST be signed by the new key 💜 generally speaking, this means including the signature value of the old key under the coverage of the new key 🌈
7.3.1. HTTP Message Signatures ✨
Okay bestie, this method is called httpsig 🦋 and you can declare it in either object form or string form.
When the proofing method is in object form, there are two parameters you get to set up 🎀:
alg: The HTTP signature algorithm, from the "HTTP Signature Algorithms" registry. REQUIRED.content-digest-alg: The algorithm used for the Content-Digest field, used to protect the content when present in the message. REQUIRED.
This example uses the Elliptic Curve Digital Signature Algorithm (ECDSA) signing algorithm over the P384 curve and the SHA-512 hashing algorithm for the content digest. It's giving main character energy 💅
{
"proof": {
"method": "httpsig",
"alg": "ecdsa-p384-sha384",
"content-digest-alg": "sha-512"
}
}
When the proofing method is in string form, the signing algorithm MUST be derived from the key material (such as using the JWS algorithm in a JWK formatted key), and the content digest algorithm MUST be sha-256. Lowkey simpler, but the rules still hit just as hard 🌸
{
"proof": "httpsig"
}
When using this method, the signer creates an HTTP message signature as described in [RFC9421] ✨ The covered components of the signature MUST include the following:
"@method": The method used in the HTTP request."@target-uri": The full request URI of the HTTP request.
When the message contains request content, the covered components MUST also include the following:
"content-digest": The Content-Digest header as defined in [RFC9530]. When the request message has content, the signer MUST calculate this field value and include the field in the request. The verifier MUST validate this field value. REQUIRED when the message request contains message content.
When the request is bound to an access token, the covered components MUST also include the following:
"authorization": The Authorization header used to present the access token as discussed in Section 7.2. 🔐
Other message components MAY also be included. You do you 🧚
The signer MUST include the tag signature parameter with the value gnap, and the verifier MUST verify that the parameter exists with this value. The signer MUST include the created signature parameter with a timestamp of when the signature was created, and the verifier MUST ensure that the creation timestamp is sufficiently close to the current time given expected network delay and clock skew. The signer SHOULD include the nonce parameter with a unique and unguessable value. When included, the verifier MUST determine that the nonce value is unique within a reasonably short time period such as several minutes. ⏰
If the signer's key presented is a JWK, the keyid parameter of the signature MUST be set to the kid value of the JWK, and the signing algorithm used MUST be the JWS algorithm denoted by the key's alg field of the JWK. 🗝️
The explicit alg signature parameter MUST NOT be included in the signature, since the algorithm will be derived from either the key material or the proof value. Don't be extra and add it 🙈
In the following non-normative example, the message content is a JSON object:
NOTE: '' line wrapping per RFC 8792
{
"access_token": {
"access": [
"dolphin-metadata"
]
},
"interact": {
"start": ["redirect"],
"finish": {
"method": "redirect",
"uri": "https://client.foo/callback",
"nonce": "VJLO6A4CAYLBXHTR0KRO"
}
},
"client": {
"key": {
"proof": "httpsig",
"jwk": {
"kid": "gnap-rsa",
"kty": "RSA",
"e": "AQAB",
"alg": "PS512",
"n": "hYOJ-XOKISdMMShn_G4W9m20mT0VWtQBsmBBkI2cmRt4Ai8Bf\
YdHsFzAtYKOjpBR1RpKpJmVKxIGNy0g6Z3ad2XYsh8KowlyVy8IkZ8NMwSrcUIBZG\
YXjHpwjzvfGvXH_5KJlnR3_uRUp4Z4Ujk2bCaKegDn11V2vxE41hqaPUnhRZxe0jR\
ETddzsE3mu1SK8dTCROjwUl14mUNo8iTrTm4n0qDadz8BkPo-uv4BC0bunS0K3bA_\
3UgVp7zBlQFoFnLTO2uWp_muLEWGl67gBq9MO3brKXfGhi3kOzywzwPTuq-cVQDyE\
N7aL0SxCb3Hc4IdqDaMg8qHUyObpPitDQ"
}
}
"display": {
"name": "My Client Display Name",
"uri": "https://client.foo/"
},
}
}
This content is hashed for the Content-Digest header using sha-256 into the following encoded value:
sha-256=:q2XBmzRDCREcS2nWo/6LYwYyjrlN1bRfv+HKLbeGAGg=:
The HTTP message signature input string is calculated to be the following 🧮
NOTE: '' line wrapping per RFC 8792
"@method": POST
"@target-uri": https://server.example.com/gnap
"content-digest": \
sha-256=:q2XBmzRDCREcS2nWo/6LYwYyjrlN1bRfv+HKLbeGAGg=:
"content-length": 988
"content-type": application/json
"@signature-params": ("@method" "@target-uri" "content-digest" \
"content-length" "content-type");created=1618884473\
;keyid="gnap-rsa";nonce="NAOEJF12ER2";tag="gnap"
This leads to the following full HTTP message request:
NOTE: '' line wrapping per RFC 8792
POST /gnap HTTP/1.1
Host: server.example.com
Content-Type: application/json
Content-Length: 988
Content-Digest: sha-256=:q2XBmzRDCREcS2nWo/6LYwYyjrlN1bRfv+HKLbeGAG\
g=:
Signature-Input: sig1=("@method" "@target-uri" "content-digest" \
"content-length" "content-type");created=1618884473\
;keyid="gnap-rsa";nonce="NAOEJF12ER2";tag="gnap"
Signature: sig1=:c2uwTa6ok3iHZsaRKl1ediKlgd5cCAYztbym68XgX8gSOgK0Bt\
+zLJ19oGjSAHDjJxX2gXP2iR6lh9bLMTfPzbFVn4Eh+5UlceP+0Z5mES7v0R1+eHe\
OqBl0YlYKaSQ11YT7n+cwPnCSdv/6+62m5zwXEEftnBeA1ECorfTuPtau/yrTYEvD\
9A/JqR2h9VzAE17kSlSSsDHYA6ohsFqcRJavX29duPZDfYgkZa76u7hJ23yVxoUpu\
2J+7VUdedN/72N3u3/z2dC8vQXbzCPTOiLru12lb6vnBZoDbUGsRR/zHPauxhj9T+\
218o5+tgwYXw17othJSxIIOZ9PkIgz4g==:
{
"access_token": {
"access": [
"dolphin-metadata"
]
},
"interact": {
"start": ["redirect"],
"finish": {
"method": "redirect",
"uri": "https://client.foo/callback",
"nonce": "VJLO6A4CAYLBXHTR0KRO"
}
},
"client": {
"key": {
"proof": "httpsig",
"jwk": {
"kid": "gnap-rsa",
"kty": "RSA",
"e": "AQAB",
"alg": "PS512",
"n": "hYOJ-XOKISdMMShn_G4W9m20mT0VWtQBsmBBkI2cmRt4Ai8Bf\
YdHsFzAtYKOjpBR1RpKpJmVKxIGNy0g6Z3ad2XYsh8KowlyVy8IkZ8NMwSrcUIBZG\
YXjHpwjzvfGvXH_5KJlnR3_uRUp4Z4Ujk2bCaKegDn11V2vxE41hqaPUnhRZxe0jR\
ETddzsE3mu1SK8dTCROjwUl14mUNo8iTrTm4n0qDadz8BkPo-uv4BC0bunS0K3bA_\
3UgVp7zBlQFoFnLTO2uWp_muLEWGl67gBq9MO3brKXfGhi3kOzywzwPTuq-cVQDyE\
N7aL0SxCb3Hc4IdqDaMg8qHUyObpPitDQ"
}
}
"display": {
"name": "My Client Display Name",
"uri": "https://client.foo/"
},
}
}
The verifier MUST ensure that the signature covers all required message components. If the HTTP message includes content, the verifier MUST calculate and verify the value of the Content-Digest header. The verifier MUST validate the signature against the expected key of the signer. Check it, check it again 🔍
A received message MAY include multiple signatures, each with its own label. The verifier MUST examine all included signatures until it finds (at least) one that is acceptable according to its policy and meets the requirements in this section. Main character verifier energy 👑
7.3.1.1. Key Rotation Using HTTP Message Signatures 🔄
When rotating a key using HTTP message signatures, the message, which includes the new public key value or reference, is first signed with the old key following all of the requirements in Section 7.3.1. The message is then signed again with the new key by following all of the requirements in Section 7.3.1 again, with the following additional requirements:
- The covered components MUST include the
SignatureandSignature-Inputvalues from the signature generated with the old key. - The tag value MUST be
gnap-rotate.
For example, the following request to the token management endpoint for rotating a token value contains the new key in the request. The message is first signed using the old key, and the resulting signature is placed in "old-key" 🗝️
NOTE: '' line wrapping per RFC 8792
POST /token/PRY5NM33 HTTP/1.1
Host: server.example.com
Authorization: GNAP 4398.34-12-asvDa.a
Content-Digest: sha-512=:Fb/A5vnawhuuJ5xk2RjGrbbxr6cvinZqd4+JPY85u/\
JNyTlmRmCOtyVhZ1Oz/cSS4tsYen6fzpCwizy6UQxNBQ==:
Signature-Input: old-key=("@method" "@target-uri" "content-digest" \
"authorization");created=1618884475;keyid="test-key-ecc-p256"\
;tag="gnap"
Signature: old-key=:vN4IKYsJl2RLFe+tYEm4dHM4R4BToqx5D2FfH4ge5WOkgxo\
dI2QRrjB8rysvoSEGvAfiVJOWsGcPD1lU639Amw==:
{
"key": {
"proof": "httpsig",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "xyz-2",
"alg": "RS256",
"n": "kOB5rR4Jv0GMeLaY6_It_r3ORwdf8ci_JtffXyaSx8xY..."
}
}
}
The signer then creates a new signature using the new key, adding the signature input and value to the signature base 💫
NOTE: '' line wrapping per RFC 8792
"@method": POST
"@target-uri": https://server.example.com/token/PRY5NM33
"content-digest": sha-512=:Fb/A5vnawhuuJ5xk2RjGrbbxr6cvinZqd4+JPY85\
u/JNyTlmRmCOtyVhZ1Oz/cSS4tsYen6fzpCwizy6UQxNBQ==:
"authorization": GNAP 4398.34-12-asvDa.a
"signature";key="old-key": :YdDJjDn2Sq8FR82e5IcOLWmmf6wILoswlnRcz+n\
M+e8xjFDpWS2YmiMYDqUdri2UiJsZx63T1z7As9Kl6HTGkQ==:
"signature-input";key="old-key": ("@method" "@target-uri" \
"content-digest" "authorization");created=1618884475\
;keyid="test-key-ecc-p256";tag="gnap"
"@signature-params": ("@method" "@target-uri" "content-digest" \
"authorization" "signature";key="old-key" "signature-input"\
;key="old-key");created=1618884480;keyid="xyz-2"
;tag="gnap-rotate"
This signature is then added to the message 📨
NOTE: '' line wrapping per RFC 8792
POST /token/PRY5NM33 HTTP/1.1
Host: server.example.com
Authorization: GNAP 4398.34-12-asvDa.a
Content-Digest: sha-512=:Fb/A5vnawhuuJ5xk2RjGrbbxr6cvinZqd4+JPY85u/\
JNyTlmRmCOtyVhZ1Oz/cSS4tsYen6fzpCwizy6UQxNBQ==:
Signature-Input: old-key=("@method" "@target-uri" "content-digest" \
"authorization");created=1618884475;keyid="test-key-ecc-p256"\
;tag="gnap", \
new-key=("@method" "@target-uri" "content-digest" \
"authorization" "signature";key="old-key" "signature-input"\
;key="old-key");created=1618884480;keyid="xyz-2"
;tag="gnap-rotate"
Signature: old-key=:vN4IKYsJl2RLFe+tYEm4dHM4R4BToqx5D2FfH4ge5WOkgxo\
dI2QRrjB8rysvoSEGvAfiVJOWsGcPD1lU639Amw==:, \
new-key=:VWUExXQ0geWeTUKhCfDT7WJyT++OHSVbfPA1ukW0o7mmstdbvIz9iOuH\
DRFzRBm0MQPFVMpLDFXQdE3vi2SL3ZjzcX2qLwzAtyRB9+RsV2caAA80A5ZGMoo\
gUsKPk4FFDN7KRUZ0vT9Mo9ycx9Dq/996TOWtAmq5z0YUYEwwn+T6+NcW8rFtms\
s1ZfXG0EoAfV6ve25p+x40Y1rvDHsfkakTRB4J8jWVDybSe39tjIKQBo3uicDVw\
twewBMNidIa+66iF3pWj8w9RSb0cncEgvbkHgASqaZeXmxxG4gM8p1HH9v/OqQT\
Oggm5gTWmCQs4oxEmWsfTOxefunfh3X+Qw==:
{
"key": {
"proof": "httpsig",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "xyz-2",
"alg": "RS256",
"n": "kOB5rR4Jv0GMeLaY6_It_r3ORwdf8ci_JtffXyaSx8xY..."
}
}
}
The verifier MUST validate both signatures before processing the request for key rotation. Both signatures get checked before any rotation happens, no shortcuts 🫶
7.3.2. Mutual TLS 🔐✨
This method is indicated by the method value mtls in string form. 💅
{
"proof": "mtls"
}
Okay so bestie, the signer shows off its TLS client certificate during the TLS negotiation with the verifier. 🎀 It's giving secure handshake vibes, obsessed!
In the following non-normative example, the certificate gets passed to the application through the Client-Cert header field from a TLS reverse proxy as per [RFC9440], and that leads to the following full HTTP request message: 🦋
POST /gnap HTTP/1.1
Host: server.example.com
Content-Type: application/jose
Content-Length: 1567
Client-Cert: \
:MIIC6jCCAdKgAwIBAgIGAXjw74xPMA0GCSqGSIb3DQEBCwUAMDYxNDAyBgNVBAMM\
K05JWU15QmpzRGp5QkM5UDUzN0Q2SVR6a3BEOE50UmppOXlhcEV6QzY2bVEwHhcN\
MjEwNDIwMjAxODU0WhcNMjIwMjE0MjAxODU0WjA2MTQwMgYDVQQDDCtOSVlNeUJq\
c0RqeUJDOVA1MzdENklUemtwRDhOdFJqaTl5YXBFekM2Nm1RMIIBIjANBgkqhkiG\
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhYOJ+XOKISdMMShn/G4W9m20mT0VWtQBsmBB\
kI2cmRt4Ai8BfYdHsFzAtYKOjpBR1RpKpJmVKxIGNy0g6Z3ad2XYsh8KowlyVy8I\
kZ8NMwSrcUIBZGYXjHpwjzvfGvXH/5KJlnR3/uRUp4Z4Ujk2bCaKegDn11V2vxE4\
1hqaPUnhRZxe0jRETddzsE3mu1SK8dTCROjwUl14mUNo8iTrTm4n0qDadz8BkPo+\
uv4BC0bunS0K3bA/3UgVp7zBlQFoFnLTO2uWp/muLEWGl67gBq9MO3brKXfGhi3k\
OzywzwPTuq+cVQDyEN7aL0SxCb3Hc4IdqDaMg8qHUyObpPitDQIDAQABMA0GCSqG\
SIb3DQEBCwUAA4IBAQBnYFK0eYHy+hVf2D58usj39lhL5znb/q9G35GBd/XsWfCE\
wHuLOSZSUmG71bZtrOcx0ptle9bp2kKl4HlSTTfbtpuG5onSa3swRNhtKtUy5NH9\
W/FLViKWfoPS3kwoEpC1XqKY6l7evoTCtS+kTQRSrCe4vbNprCAZRxz6z1nEeCgu\
NMk38yTRvx8ihZpVOuU+Ih+dOtVe/ex5IAPYxlQsvtfhsUZqc7IyCcy72WHnRHlU\
fn3pJm0S5270+Yls3Iv6h3oBAP19i906UjiUTNH3g0xMW+V4uLxgyckt4wD4Mlyv\
jnaQ7Z3sR6EsXMocAbXHIAJhwKdtU/fLgdwL5vtx:
{
"access_token": {
"access": [
"dolphin-metadata"
]
},
"interact": {
"start": ["redirect"],
"finish": {
"method": "redirect",
"uri": "https://client.foo/callback",
"nonce": "VJLO6A4CAYLBXHTR0KRO"
}
},
"client": {
"key": {
"proof": "mtls",
"cert": "MIIC6jCCAdKgAwIBAgIGAXjw74xPMA0GCSqGSIb3DQEBCwUAMD\
YxNDAyBgNVBAMMK05JWU15QmpzRGp5QkM5UDUzN0Q2SVR6a3BEOE50UmppOXlhcEV\
6QzY2bVEwHhcNMjEwNDIwMjAxODU0WhcNMjIwMjE0MjAxODU0WjA2MTQwMgYDVQQD\
DCtOSVlNeUJqc0RqeUJDOVA1MzdENklUemtwRDhOdFJqaTl5YXBFekM2Nm1RMIIBI\
jANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhYOJ+XOKISdMMShn/G4W9m20mT\
0VWtQBsmBBkI2cmRt4Ai8BfYdHsFzAtYKOjpBR1RpKpJmVKxIGNy0g6Z3ad2XYsh8\
KowlyVy8IkZ8NMwSrcUIBZGYXjHpwjzvfGvXH/5KJlnR3/uRUp4Z4Ujk2bCaKegDn\
11V2vxE41hqaPUnhRZxe0jRETddzsE3mu1SK8dTCROjwUl14mUNo8iTrTm4n0qDad\
z8BkPo+uv4BC0bunS0K3bA/3UgVp7zBlQFoFnLTO2uWp/muLEWGl67gBq9MO3brKX\
fGhi3kOzywzwPTuq+cVQDyEN7aL0SxCb3Hc4IdqDaMg8qHUyObpPitDQIDAQABMA0\
GCSqGSIb3DQEBCwUAA4IBAQBnYFK0eYHy+hVf2D58usj39lhL5znb/q9G35GBd/Xs\
WfCEwHuLOSZSUmG71bZtrOcx0ptle9bp2kKl4HlSTTfbtpuG5onSa3swRNhtKtUy5\
NH9W/FLViKWfoPS3kwoEpC1XqKY6l7evoTCtS+kTQRSrCe4vbNprCAZRxz6z1nEeC\
guNMk38yTRvx8ihZpVOuU+Ih+dOtVe/ex5IAPYxlQsvtfhsUZqc7IyCcy72WHnRHl\
Ufn3pJm0S5270+Yls3Iv6h3oBAP19i906UjiUTNH3g0xMW+V4uLxgyckt4wD4Mlyv\
jnaQ7Z3sR6EsXMocAbXHIAJhwKdtU/fLgdwL5vtx"
}
"display": {
"name": "My Client Display Name",
"uri": "https://client.foo/"
},
},
"subject": {
"formats": ["iss_sub", "opaque"]
}
}
Here's the vibe check: the verifier compares the TLS client certificate it got during MTLS negotiation with the expected key of the signer. 🫶 Since the TLS connection covers the entire message, there are no extra requirements to check. ✨
Note that the verifier often won't do a full certificate chain validation on the presented TLS client certificate 🙈, because the trust for this cert could live outside a PKI system, like a static registration or trust-on-first-use. See Sections 11.3 and 11.4 for some extra considerations for this key proofing method. 💜
7.3.2.1. Key Rotation Using MTLS 🔄
So here's the tea: it isn't possible to present two client-authenticated certificates to an MTLS connection at the same time, so dynamic key rotation for this proofing method is not defined. 💀 Instead, key rotation for MTLS-based client instances is expected to be managed through deployment practices, as discussed in Section 11.4. 🌸
7.3.3. Detached JWS 🦋
This method is indicated by the method value jwsd in string form. 💖
{
"proof": "jwsd"
}
Okay so the signer makes a JSON Web Signature (JWS) [RFC7515] object like this. 🎀
To protect the request, the JOSE header of the signature contains the following claims: 👑
kid(string): The key identifier. REQUIRED if the key is presented in JWK format. This MUST be the value of thekidfield of the key. 🔑alg(string): The algorithm used to sign the request. The algorithm MUST be appropriate to the key presented. If the key is presented as a JWK, this MUST be equal to thealgparameter of the key. The algorithm MUST NOT benone. REQUIRED. 🙅♀️typ(string): The type header, value"gnap-binding-jwsd". REQUIRED. 🏷️htm(string): The HTTP method used to make this request, as a case-sensitive ASCII string. Note that most public HTTP methods are in uppercase ASCII by convention. REQUIRED. 🍓uri(string): The HTTP URI used for this request. This value MUST be an absolute URI, including all path and query components and no fragment components. REQUIRED. 🌈created(integer): A timestamp of when the signature was created, in integer seconds since UNIX Epoch. REQUIRED. ⏰
When the request is bound to an access token, the JOSE header MUST also include the following: ✨
ath(string): The hash of the access token. The value MUST be the result of base64url encoding (with no padding) the SHA-256 digest of the ASCII encoding of the associated access token's value. REQUIRED. 🧚
If the HTTP request has content (such as an HTTP POST or PUT method), the payload of the JWS object is the base64url encoding (without padding) of the SHA-256 digest of the bytes of the content. If the request being made does not have content (such as an HTTP GET, OPTIONS, or DELETE method), the JWS signature is calculated over an empty payload. 📦
The signer presents the signed object in compact form [RFC7515] in the Detached-JWS header field. 💅
In the following non-normative example, the JOSE header contains these parameters: 🔍
{
"alg": "RS256",
"kid": "gnap-rsa",
"uri": "https://server.example.com/gnap",
"htm": "POST",
"typ": "gnap-binding-jwsd",
"created": 1618884475
}
The request content is the following JSON object: 📝
NOTE: '' line wrapping per RFC 8792
{
"access_token": {
"access": [
"dolphin-metadata"
]
},
"interact": {
"start": ["redirect"],
"finish": {
"method": "redirect",
"uri": "https://client.foo/callback",
"nonce": "VJLO6A4CAYLBXHTR0KRO"
}
},
"client": {
"key": {
"proof": "jwsd",
"jwk": {
"kid": "gnap-rsa",
"kty": "RSA",
"e": "AQAB",
"alg": "RS256",
"n": "hYOJ-XOKISdMMShn_G4W9m20mT0VWtQBsmBBkI2cmRt4Ai8Bf\
YdHsFzAtYKOjpBR1RpKpJmVKxIGNy0g6Z3ad2XYsh8KowlyVy8IkZ8NMwSrcUIBZG\
YXjHpwjzvfGvXH_5KJlnR3_uRUp4Z4Ujk2bCaKegDn11V2vxE41hqaPUnhRZxe0jR\
ETddzsE3mu1SK8dTCROjwUl14mUNo8iTrTm4n0qDadz8BkPo-uv4BC0bunS0K3bA_\
3UgVp7zBlQFoFnLTO2uWp_muLEWGl67gBq9MO3brKXfGhi3kOzywzwPTuq-cVQDyE\
N7aL0SxCb3Hc4IdqDaMg8qHUyObpPitDQ"
}
}
"display": {
"name": "My Client Display Name",
"uri": "https://client.foo/"
},
}
}
This is hashed to the following base64-encoded value: 🧮
PGiVuOZUcN1tRtUS6tx2b4cBgw9mPgXG3IPB3wY7ctc
This leads to the following full HTTP request message: 🎁
NOTE: '' line wrapping per RFC 8792
POST /gnap HTTP/1.1
Host: server.example.com
Content-Type: application/json
Content-Length: 983
Detached-JWS: eyJhbGciOiJSUzI1NiIsImNyZWF0ZWQiOjE2MTg4ODQ0NzUsImh0b\
SI6IlBPU1QiLCJraWQiOiJnbmFwLXJzYSIsInR5cCI6ImduYXAtYmluZGluZytqd3\
NkIiwidXJpIjoiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20vZ25hcCJ9.PGiVuO\
ZUcN1tRtUS6tx2b4cBgw9mPgXG3IPB3wY7ctc.fUq-SV-A1iFN2MwCRW_yolVtT2_\
TZA2h5YeXUoi5F2Q2iToC0Tc4drYFOSHIX68knd68RUA7yHqCVP-ZQEd6aL32H69e\
9zuMiw6O_s4TBKB3vDOvwrhYtDH6fX2hP70cQoO-47OwbqP-ifkrvI3hVgMX9TfjV\
eKNwnhoNnw3vbu7SNKeqJEbbwZfpESaGepS52xNBlDNMYBQQXxM9OqKJaXffzLFEl\
-Xe0UnfolVtBraz3aPrPy1C6a4uT7wLda3PaTOVtgysxzii3oJWpuz0WP5kRujzDF\
wX_EOzW0jsjCSkL-PXaKSpZgEjNjKDMg9irSxUISt1C1T6q3SzRgfuQ
{
"access_token": {
"access": [
"dolphin-metadata"
]
},
"interact": {
"start": ["redirect"],
"finish": {
"method": "redirect",
"uri": "https://client.foo/callback",
"nonce": "VJLO6A4CAYLBXHTR0KRO"
}
},
"client": {
"key": {
"proof": "jwsd",
"jwk": {
"kid": "gnap-rsa",
"kty": "RSA",
"e": "AQAB",
"alg": "RS256",
"n": "hYOJ-XOKISdMMShn_G4W9m20mT0VWtQBsmBBkI2cmRt4Ai8Bf\
YdHsFzAtYKOjpBR1RpKpJmVKxIGNy0g6Z3ad2XYsh8KowlyVy8IkZ8NMwSrcUIBZG\
YXjHpwjzvfGvXH_5KJlnR3_uRUp4Z4Ujk2bCaKegDn11V2vxE41hqaPUnhRZxe0jR\
ETddzsE3mu1SK8dTCROjwUl14mUNo8iTrTm4n0qDadz8BkPo-uv4BC0bunS0K3bA_\
3UgVp7zBlQFoFnLTO2uWp_muLEWGl67gBq9MO3brKXfGhi3kOzywzwPTuq-cVQDyE\
N7aL0SxCb3Hc4IdqDaMg8qHUyObpPitDQ"
}
}
"display": {
"name": "My Client Display Name",
"uri": "https://client.foo/"
},
}
}
When the verifier gets the Detached-JWS header, it MUST parse and validate the JWS object. 🕵️♀️ The signature MUST be validated against the expected key of the signer. If the HTTP message request contains content, the verifier MUST calculate the hash of the content just as the signer does, with no normalization or transformation of the request. All required fields MUST be present, and their values MUST be valid. All fields MUST match the corresponding portions of the HTTP message. For example, the htm field of the JWS header has to be the same as the HTTP verb used in the request. 💯
Note that this proofing method depends on a specific cryptographic algorithm, SHA-256, in two ways: 1) the ath hash algorithm is hardcoded and 2) the payload of the detached/attached signature is computed using a hardcoded hash. 🧐 A future version of this document may address crypto-agility for both these uses by replacing ath with a new header that upgrades the algorithm and possibly defining a new JWS header that indicates the HTTP content's hash method. 🔮
7.3.3.1. Key Rotation Using Detached JWS 🔁
When rotating a key using detached JWS, the message, which includes the new public key value or reference, is first signed with the old key as described above, using a JWS object with typ header value "gnap-binding-rotation-jwsd". 🌟 The value of that JWS object is then taken as the payload of a new JWS object, to be signed by the new key using the parameters above. 💫
The value of the new JWS object is sent in the Detached-JWS header. 🎀
7.3.4. Attached JWS ✨
bestie, this method gets picked by using the method value jws in string form 💅
{
"proof": "jws"
}
the signer makes a JWS [RFC7515] object like this 🦋
to protect the request, the JWS header has these claims:
kid(string): the key identifier. REQUIRED if the key is presented in JWK format. This MUST be the value of thekidfield of the key.alg(string): the algorithm used to sign the request. MUST be appropriate to the key presented. If the key is presented as a JWK, this MUST be equal to thealgparameter of the key. MUST NOT benone. REQUIRED. 🔥typ(string): the type header, valuegnap-binding-jws. REQUIRED.htm(string): the HTTP method used to make this request, as a case-sensitive ASCII string. (Note that most public HTTP methods are in uppercase.) REQUIRED.uri(string): the HTTP URI used for this request, including all path and query components and no fragment components. REQUIRED.created(integer): a timestamp of when the signature was created, in integer seconds since UNIX Epoch. REQUIRED. ⭐
when the request is bound to an access token, the JOSE header MUST also include the following:
ath(string): the hash of the access token. The value MUST be the result of base64url encoding (with no padding) the SHA-256 digest of the ASCII encoding of the associated access token's value. REQUIRED. 🌸
if the HTTP request has content (like an HTTP POST or PUT method), the payload of the JWS object is the JSON serialized content of the request. The object gets signed according to JWS and serialized into compact form [RFC7515]. The signer presents the JWS as the content of the request with a content type of application/jose. The verifier MUST extract the payload of the JWS and treat it as the request content for further processing. 🫶
if the request being made does not have content (like an HTTP GET, OPTIONS, or DELETE method), the JWS signature is calculated over an empty payload and passed in the Detached-JWS header, as described in Section 7.3.3. 🦋
here's a non-normative example, where the JOSE header contains these parameters:
{
"alg": "RS256",
"kid": "gnap-rsa",
"uri": "https://server.example.com/gnap",
"htm": "POST",
"typ": "gnap-binding-jws",
"created": 1618884475
}
the request content, used as the JWS Payload, is this JSON object:
NOTE: '\' line wrapping per RFC 8792
{
"access_token": {
"access": [
"dolphin-metadata"
]
},
"interact": {
"start": ["redirect"],
"finish": {
"method": "redirect",
"uri": "https://client.foo/callback",
"nonce": "VJLO6A4CAYLBXHTR0KRO"
}
},
"client": {
"key": {
"proof": "jws",
"jwk": {
"kid": "gnap-rsa",
"kty": "RSA",
"e": "AQAB",
"alg": "RS256",
"n": "hYOJ-XOKISdMMShn_G4W9m20mT0VWtQBsmBBkI2cmRt4Ai8Bf\
YdHsFzAtYKOjpBR1RpKpJmVKxIGNy0g6Z3ad2XYsh8KowlyVy8IkZ8NMwSrcUIBZG\
YXjHpwjzvfGvXH_5KJlnR3_uRUp4Z4Ujk2bCaKegDn11V2vxE41hqaPUnhRZxe0jR\
ETddzsE3mu1SK8dTCROjwUl14mUNo8iTrTm4n0qDadz8BkPo-uv4BC0bunS0K3bA_\
3UgVp7zBlQFoFnLTO2uWp_muLEWGl67gBq9MO3brKXfGhi3kOzywzwPTuq-cVQDyE\
N7aL0SxCb3Hc4IdqDaMg8qHUyObpPitDQ"
}
}
"display": {
"name": "My Client Display Name",
"uri": "https://client.foo/"
},
},
"subject": {
"formats": ["iss_sub", "opaque"]
}
}
and this leads to the following full HTTP request message 🎀
NOTE: '\' line wrapping per RFC 8792
POST /gnap HTTP/1.1
Host: server.example.com
Content-Type: application/jose
Content-Length: 1047
eyJhbGciOiJSUzI1NiIsImNyZWF0ZWQiOjE2MTg4ODQ0NzUsImh0bSI6IlBPU1QiLCJ\
raWQiOiJnbmFwLXJzYSIsInR5cCI6ImduYXAtYmluZGluZytqd3NkIiwidXJpIjoiaH\
R0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20vZ25hcCJ9.CnsKICAgICJhY2Nlc3NfdG9r\
ZW4iOiB7CiAgICAgICAgImFjY2VzcyI6IFsKICAgICAgICAgICAgImRvbHBoaW4tbWV\
0YWRhdGEiCiAgICAgICAgXQogICAgfSwKICAgICJpbnRlcmFjdCI6IHsKICAgICAgIC\
Aic3RhcnQiOiBbInJlZGlyZWN0Il0sCiAgICAgICAgImZpbmlzaCI6IHsKICAgICAgI\
CAgICAgIm1ldGhvZCI6ICJyZWRpcmVjdCIsCiAgICAgICAgICAgICJ1cmkiOiAiaHR0\
cHM6Ly9jbGllbnQuZm9vL2NhbGxiYWNrIiwKICAgICAgICAgICAgIm5vbmNlIjogIlZ\
KTE82QTRDQVlMQlhIVFIwS1JPIgogICAgICAgIH0KICAgIH0sCiAgICAiY2xpZW50Ij\
ogewogICAgICAicHJvb2YiOiAiandzIiwKICAgICAgImtleSI6IHsKICAgICAgICAia\
ndrIjogewogICAgICAgICAgICAia2lkIjogImduYXAtcnNhIiwKICAgICAgICAgICAg\
Imt0eSI6ICJSU0EiLAogICAgICAgICAgICAiZSI6ICJBUUFCIiwKICAgICAgICAgICA\
gImFsZyI6ICJSUzI1NiIsCiAgICAgICAgICAgICJuIjogImhZT0otWE9LSVNkTU1TaG\
5fRzRXOW0yMG1UMFZXdFFCc21CQmtJMmNtUnQ0QWk4QmZZZEhzRnpBdFlLT2pwQlIxU\
nBLcEptVkt4SUdOeTBnNlozYWQyWFlzaDhLb3dseVZ5OElrWjhOTXdTcmNVSUJaR1lY\
akhwd2p6dmZHdlhIXzVLSmxuUjNfdVJVcDRaNFVqazJiQ2FLZWdEbjExVjJ2eEU0MWh\
xYVBVbmhSWnhlMGpSRVRkZHpzRTNtdTFTSzhkVENST2p3VWwxNG1VTm84aVRyVG00bj\
BxRGFkejhCa1BvLXV2NEJDMGJ1blMwSzNiQV8zVWdWcDd6QmxRRm9GbkxUTzJ1V3Bfb\
XVMRVdHbDY3Z0JxOU1PM2JyS1hmR2hpM2tPenl3endQVHVxLWNWUUR5RU43YUwwU3hD\
YjNIYzRJZHFEYU1nOHFIVXlPYnBQaXREUSIKICAgICAgICB9CiAgICAgIH0KICAgICA\
gImRpc3BsYXkiOiB7CiAgICAgICAgIm5hbWUiOiAiTXkgQ2xpZW50IERpc3BsYXkgTm\
FtZSIsCiAgICAgICAgInVyaSI6ICJodHRwczovL2NsaWVudC5mb28vIgogICAgICB9L\
AogICAgfSwKICAgICJzdWJqZWN0IjogewogICAgICAgICJmb3JtYXRzIjogWyJpc3Nf\
c3ViIiwgIm9wYXF1ZSJdCiAgICB9Cn0K.MwNoVMQp5hVxI0mCs9LlOUdFtkDXaA1_eT\
vOXq7DOGrtDKH7q4vP2xUq3fH2jRAZqnobo0WdPP3eM3NH5QUjW8pa6_QpwdIWkK7r-\
u_52puE0lPBp7J4U2w4l9gIbg8iknsmWmXeY5F6wiGT8ptfuEYGgmloAJd9LIeNvD3U\
LW2h2dz1Pn2eDnbyvgB0Ugae0BoZB4f69fKWj8Z9wvTIjk1LZJN1PcL7_zT8Lrlic9a\
PyzT7Q9ovkd1s-4whE7TrnGUzFc5mgWUn_gsOpsP5mIIljoEEv-FqOW2RyNYulOZl0Q\
8EnnDHV_vPzrHlUarbGg4YffgtwkQhdK72-JOxYQ
when the verifier gets an attached JWS request, it MUST parse and validate the JWS object. The signature MUST be validated against the expected key of the signer. All required fields MUST be present, and their values MUST be valid. All fields MUST match the corresponding portions of the HTTP message. For example, the htm field of the JWS header has to be the same as the HTTP verb used in the request. 👑
ngl this part is a big deal: this proofing method depends on one specific cryptographic algorithm, SHA-256, in two ways. Theathhash algorithm is hardcoded, and computing the payload of the detached/attached signature also uses a hardcoded hash. A future version of this document may address crypto-agility for both of these uses by replacingathwith a new header that upgrades the algorithm, and possibly by defining a new header that indicates the HTTP content's hash method. 💀
7.3.4.1. Key Rotation Using Attached JWS 🔄
when you rotate a key using attached JWS, the message (which includes the new public key value or reference) is first signed with the old key, using a JWS object with typ header value gnap-binding-rotation-jws. Then the value of that JWS object gets taken as the payload of a new JWS object, which is signed by the new key. 🌈
8. Resource Access Rights ✨
okay bestie, this is the part where GNAP gets to be so organized about what a token is actually allowed to do 💅 GNAP gives you a rich structure for describing the protected resources that RSs host and that client software wants to touch 🦋 This structure shows up in two places: when the client instance asks for an access token (Section 2.1), and when the access token comes back (Section 3.2). And the whole thing is designed to be analogous to the OAuth 2.0 Rich Authorization Requests data structure from [RFC9396], so if you've seen that, you'll feel right at home 🫶
The root of it all is a JSON array 🎀 Each element in that array is a right of access tied to the access token. An RS can define an individual right as either an object or a string 🌸 And the resulting access is the union of every element in the array. So more elements means more access, no cap 🔥
Each object holds multiple dimensions of access, and every object has a REQUIRED type property. That type decides what kind of API the token is for, and it also decides the shape of the rest of the object 💖 Different type definitions don't have any expected interoperability with each other, so don't assume they'll play nicely 💀
type (string): The type of resource request as a string. This field MAY define which other fields are allowed in the request object. REQUIRED. 🧚
The AS controls the value of the type field 👑 The AS MUST compare it using an exact byte match of the string value against the types it knows, and the AS MUST make sure there's no collision between the different authorization data types it supports. The AS MUST NOT do any collation or normalization of data types during that comparison, so no sneaky case-folding or cleanup. It's RECOMMENDED that API designers for general-purpose APIs use a URI for this field, which helps avoid collisions when multiple API types sit behind a single AS 🌈
Lots of APIs will have their own properties, but this spec also defines a set of common data fields that are meant to work across different types of APIs ✨ An API definition doesn't have to use these common fields. They're just reusable building blocks for API designers to grab if they want them 🍓 The allowable values for every field are decided by the API being protected, as defined by a particular type value.
actions (array of strings): The types of actions the client instance will take at the RS as an array of strings (for example, a client instance asking for a combination of "read" and "write" access). 💅
locations (array of strings): The location of the RS as an array of strings. These strings are typically URIs identifying the location of the RS. 📍
datatypes (array of strings): The kinds of data available to the client instance at the RS's API as an array of strings (for example, a client instance asking for access to raw "image" data and "metadata" at a photograph API). 📸
identifier (string): A string identifier indicating a specific resource at the RS (for example, a patient identifier for a medical API or a bank account number for a financial API). 🔑
privileges (array of strings): The types or levels of privilege being requested at the resource (for example, a client instance asking for administrative-level access or access when the RO is no longer online). 👑
Here's a non-normative example with three kinds of access (read, write, and delete) to each of two different locations and two different data types (metadata and images) for a single access token, using the fictitious photo-api type definition 🦋
"access": [
{
"type": "photo-api",
"actions": [
"read",
"write",
"delete"
],
"locations": [
"https://server.example.net/",
"https://resource.local/other"
],
"datatypes": [
"metadata",
"images"
]
}
]
Now here's the lowkey tricky part 👀 The exact meaning of an access request object depends on its type definition, but the access requested for each object in the array is expected to be the cross-product of all of its fields ✨ So the object is asking for all the listed actions, at all the listed locations, for all the listed datatypes, together. Assuming the request above got granted, the client instance could do a read on the images from the first server, a delete on the metadata from the second server, or any other combo of those fields, all with the same access token 🤯
If you want a different combo, like one action at one location and a different set of actions at another location, just include multiple separate objects in the resources array 🎀 The total access for the token is the union of all those objects. Here's a non-normative example using the same fictitious photo-api type to request one token with more targeted rights, using two separate objects 💖
"access": [
{
"type": "photo-api",
"actions": [
"read"
],
"locations": [
"https://server.example.net/"
],
"datatypes": [
"images"
]
},
{
"type": "photo-api",
"actions": [
"write",
"delete"
],
"locations": [
"https://resource.local/other"
],
"datatypes": [
"metadata"
]
}
]
So this is giving read access to images on one server, plus write and delete access to metadata on a different server. And notice it does not ask for write or delete on the images of the first server. Precision is everything 💅
API designers can mix common fields from this spec with fields that are specific to their own API ✨ This non-normative example shows both kinds in two different fictitious API types 🌸 The first request has the common actions, locations, and datatypes fields, plus the API-specific geolocation field. The second one has the common actions and identifier fields, plus the API-specific currency field 🧚
"access": [
{
"type": "photo-api",
"actions": [
"read",
"write"
],
"locations": [
"https://server.example.net/",
"https://resource.local/other"
],
"datatypes": [
"metadata",
"images"
],
"geolocation": [
{ lat: -32.364, lng: 153.207 },
{ lat: -35.364, lng: 158.207 }
]
},
{
"type": "financial-transaction",
"actions": [
"withdraw"
],
"identifier": "account-14-32-32-3",
"currency": "USD"
}
]
If this request gets approved, the token's access rights will be the union of the requested access for each of the two APIs, just like before 🎉
8.1. Requesting Resources by Reference 🔖
Instead of sending an object that describes the requested resource (Section 8), access rights MAY be communicated as a string that the AS knows about, representing the access being requested 💅 Just like access rights sent as objects, access rights sent as reference strings point to a specific access at a protected resource. In this non-normative example, three distinct resource access rights are requested 🦋
"access": [
"read", "dolphin-metadata", "some other thing"
]
This value is opaque to the client instance and MAY be any valid JSON string. So it could include spaces, Unicode characters, and properly escaped string sequences 🌈 That said, sometimes the value is meant to be read and understood by the developer writing the client software. In those cases, the API designer who picks a human-readable string SHOULD take steps so the values aren't easily confused by a developer, like limiting the strings to easily disambiguated characters 🙈
This works a lot like OAuth 2.0's scope parameter [RFC6749], where one string stands for the whole set of access rights the client instance is requesting 💜 So the reference string could contain any valid OAuth 2.0 scope value, as in Appendix B.5. Just note that the reference string here is not bound to the same character restrictions as OAuth 2.0's scope definition ✨
A single access array MAY include both object-type and string-type resource items 🎀 In this non-normative example, the client instance asks for access to a photo-api and a financial-transaction API type, plus the reference values read, dolphin-metadata, and some other thing 🐬
"access": [
{
"type": "photo-api",
"actions": [
"read",
"write",
"delete"
],
"locations": [
"https://server.example.net/",
"https://resource.local/other"
],
"datatypes": [
"metadata",
"images"
]
},
"read",
"dolphin-metadata",
{
"type": "financial-transaction",
"actions": [
"withdraw"
],
"identifier": "account-14-32-32-3",
"currency": "USD"
},
"some other thing"
]
The requested access is the union of all elements in the array, both objects and reference strings together 🌟
To help people use both objects and reference strings for the same kind of API, the API designer can set up a clear mapping between the two forms 🧩 One option for picking reference string values is to reuse the type parameter from the fully specified object, with the API defining a set of default behaviors for that case. For example, an API definition could declare the following string:
"access": [
"photo-api"
]
as being equivalent to the following fully defined object:
"access": [
{
"type": "photo-api",
"actions": [ "read", "write", "delete" ],
"datatypes": [ "metadata", "image" ]
}
]
The exact way reference strings get related is up to the API designer 💖 The AS enforces these, and the details are out of scope for this specification 🫶
Okay that's the end of this part, bestie. Keep those tokens scoped and slay the rest of the spec 🌈💅✨
9. Discovery ✨
okay so bestie, GNAP is literally built to skip the whole "figure everything out first" phase 💅 🦋 To kick off a request, the client instance only needs two things: the grant endpoint of the AS (one single URI 🌸) and the keys it's going to sign the request with 🔑. Everything else gets negotiated on the fly in the middle of the protocol. so iconic, no cap 👑
BUT the AS can have limits on what it's able to do, so if the client instance wants to make its calls to the AS more efficient before it sends a request, it MAY send an HTTP OPTIONS request to the grant request endpoint to grab the server's discovery info 🧚 The AS MUST answer with a JSON document, Content-Type application/json, holding a single object with these fields:
grant_request_endpoint (string): The location of the AS's grant request endpoint. The location MUST be an absolute URL [RFC3986] with a scheme component (which MUST be "https"), a host component, and optionally port, path, and query components and no fragment components. This URL MUST match the URL the client instance used to make the discovery request. REQUIRED. 💖
interaction_start_modes_supported (array of strings): A list of the AS's interaction start methods. The values of this list correspond to the possible values for the interaction start field of the request (Section 2.5.1) and MUST be values from the "GNAP Interaction Start Modes" registry (Section 10.9). OPTIONAL. ✨
interaction_finish_methods_supported (array of strings): A list of the AS's interaction finish methods. The values of this list correspond to the possible values for the method element of the interaction finish field of the request (Section 2.5.2) and MUST be values from the "GNAP Interaction Finish Methods" registry (Section 10.10). OPTIONAL. 🎀
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 the proof field of the key section of the request (Section 7.1) and MUST be values from the "GNAP Key Proofing Methods" registry (Section 10.16). OPTIONAL. 🔐
sub_id_formats_supported (array of strings): A list of the AS's supported Subject Identifier formats. The values of this list correspond to possible values of the Subject Identifier field of the request (Section 2.2) and MUST be values from the "Subject Identifier Formats" registry [Subj-ID-Formats]. OPTIONAL. 🌈
assertion_formats_supported (array of strings): A list of the AS's supported assertion formats. The values of this list correspond to possible values of the subject assertion field of the request (Section 2.2) and MUST be values from the "GNAP Assertion Formats" registry (Section 10.6). OPTIONAL. 🦋
key_rotation_supported (boolean): The boolean "true" means the AS supports rotating access token bound keys by the client (Section 6.1.1). If the field is missing, or it's a boolean "false", the feature is NOT supported. 🙈
⚠️ the discovery info is ONLY for optimization! The AS MAY deny any request, or any portion of a request, even if it lists a capability as supported. Example: if a client can be registered with the mtls key proofing mechanism but the AS also returns other proofing methods in the discovery doc, the AS will still deny a request from that client using a different proofing mechanism. Same deal with key_rotation_supported set to "true": the AS can still deny a rotation request for a variety of reasons 💀
Additional fields can be defined in the "GNAP Authorization Server Discovery Fields" registry (Section 10.18). 🍓
9.1. RS-First Method of AS Discovery 🌸
okay so if the client instance calls an RS without an access token, or with a bad one, the RS SHOULD be super clear that GNAP is needed to get into the resource. It does this by responding with the WWW-Authenticate header field and a GNAP challenge 💅
Sometimes the client instance wants to know which specific AS it should negotiate with to get access to that RS. In that case the RS MAY also return these OPTIONAL parameters:
as_uri: The URI of the grant endpoint of the GNAP AS. The client instance uses it to call the AS and request an access token. 🧚
referrer: The URI of the GNAP RS. The client instance sends it in the Referer header as part of the grant request. 💜
access: An opaque access reference as defined in Section 8.1. It MUST be sufficient for at least the action the client instance was trying to take at the RS, and it MAY allow additional access rights as well. The client sends it as an access right in the grant request. ✨
The client instance SHOULD then use both the referrer and access parameters in its access token request. The client instance MUST check that the referrer parameter is equal to the URI of the RS, using the simple string comparison method in Section 6.2.1 of [RFC3986]. 🔥
How the RS figures out the value for the access reference is out of scope for this spec, but some dynamic methods are covered in [GNAP-RS]. 🌈
When the client gets this response from the RS:
NOTE: '' line wrapping per RFC 8792
WWW-Authenticate: \
GNAP as_uri=https://as.example/tx\
;access=FWWIKYBQ6U56NL1\
;referrer=https://rs.example
the client instance then makes a request to the as_uri as described in Section 2, passing the value of referrer as an HTTP Referer header field and putting the access reference unchanged into the access array in the access_token portion of the request. The client instance MAY ask for additional resources and other info too 🫶
Here's a non-normative example (not official, just vibes 🙈). The client is requesting a single access token using the opaque access reference FWWIKYBQ6U56NL1 it got from the RS, plus the dolphin-metadata it was configured with out of band:
POST /tx HTTP/1.1
Host: as.example
Referer: https://rs.example/resource
Content-Type: application/json
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
{
"access_token": {
"access": [
"FWWIKYBQ6U56NL1",
"dolphin-metadata"
]
},
"client": "KHRS6X63AJ7C7C4AZ9AO"
}
The client includes the Referer header field so the AS knows this process started from a discovery at the RS 🦋
If it's issued, the resulting access token would have enough access to be used at both referenced resources. 👑
Security alert 🚨 the potential of a compromised RS (Section 11.37) redirecting the requests of an otherwise properly authenticated client is a BIG deal, so think it through carefully before allowing this discovery process. You can reduce that risk with an alternative pre-registration process, so the client knows which AS protects which RS. There are also privacy considerations around revealing which AS is protecting a given resource; those are discussed in Section 12.4.1. 💀😭
9.2. Dynamic Grant Endpoint Discovery 🎀
Other ways to discover the right grant endpoint for a given application are outside the scope of this spec. That's intentional, since lots of apps rely on static config between the client instance and AS, same as in OAuth 2.0 💖 That said, GNAP's dynamic nature makes it a prime candidate for extensions that define ways to discover the AS grant endpoint at runtime. Advanced use cases could define contextual methods for securely handing this endpoint to the client instance. Also, GNAP is intentionally designed so the client instance only needs to know the grant endpoint and not any other parameters, since other functions and values can be shared and negotiated during the grant process 🧚✨
10. IANA Considerations 🌸
IANA has added values to existing registries and created 16 registries for GNAP [GNAP-REG], and it populated those registries with the initial values described in this section. 🍓
All use of value typing is based on data types in [RFC8259] and MUST be one of the following: number, object, string, boolean, or array. When the type is array, the contents of the array MUST be specified, like "array of objects" when one subtype is allowed or "array of strings/objects" when multiple simultaneous subtypes are allowed. When the type is object, the structure of the object MUST be specified in the definition. If a parameter is available in different types, each type SHOULD be registered separately. 💅
General guidance for extension parameters is in Appendix D. 🦋
10.1. HTTP Authentication Scheme Registration 🔐
IANA has registered the following scheme in the "HTTP Authentication Schemes" registry [Auth-Schemes] defined in Section 18.5 of [HTTP]:
Authentication Scheme Name: GNAP
Reference: Section 7.2 of RFC 9635 ✨
10.2. Media Type Registration 💜
Per this section, IANA has registered the following media types [RFC2046] in the "Media Types" registry [MediaTypes] as described in [RFC6838]. 🎀
10.2.1. application/gnap-binding-jwsd 🌈
This media type says the content is a GNAP message to be bound with a detached JWS mechanism. 🫶
Type name: application
Subtype name: gnap-binding-jwsd
Required parameters: N/A
Optional parameters: N/A
Encoding considerations: binary
Security considerations: See Section 11 of RFC 9635.
Interoperability considerations: N/A
Published specification: RFC 9635
Applications that use this media type: GNAP
Fragment identifier considerations: N/A
Additional information:
Deprecated alias names for this type: N/A Magic number(s): N/A File extension(s): N/A Macintosh file type code(s): N/A
Person & email address to contact for further information: IETF GNAP Working Group (txauth@ietf.org)
Intended usage: COMMON
Restrictions on usage: none
Author: IETF GNAP Working Group (txauth@ietf.org)
Change Controller: IETF
10.2.2. application/gnap-binding-jws 👑
This media type says the content is a GNAP message to be bound with an attached JWS mechanism. 💖
Type name: application
Subtype name: gnap-binding-jws
Required parameters: N/A
Optional parameters: N/A
Encoding considerations: binary
Security considerations: See Section 11 of RFC 9635.
Interoperability considerations: N/A
Published specification: RFC 9635
Applications that use this media type: GNAP
Fragment identifier considerations: N/A
Additional information:
Deprecated alias names for this type: N/A Magic number(s): N/A File extension(s): N/A Macintosh file type code(s): N/A
Person & email address to contact for further information: IETF GNAP Working Group (txauth@ietf.org)
Intended usage: COMMON
Restrictions on usage: none
Author: IETF GNAP Working Group (txauth@ietf.org)
Change Controller: IETF
10.2.3. application/gnap-binding-rotation-jwsd ✨
okay so this media type is for a GNAP token rotation message that gets bound with a detached JWS mechanism 🦋 it's giving "keep the signature separate from the message," and it's very clean 💅
- Type name:
application - Subtype name:
gnap-binding-rotation-jwsd - Required parameters:
N/A - Optional parameters:
N/A - Encoding considerations:
binary - Security considerations: See Section 11 of RFC 9635. 😭 the security section is the main character here, so go read it
- Interoperability considerations:
N/A - Published specification:
RFC 9635 - Applications that use this media type:
GNAP - Fragment identifier considerations:
N/A - Additional information:
- Deprecated alias names for this type:
N/A - Magic number(s):
N/A - File extension(s):
N/A - Macintosh file type code(s):
N/A
- Deprecated alias names for this type:
- Person & email address to contact for further information: IETF GNAP Working Group (
txauth@ietf.org) - Intended usage:
COMMON - Restrictions on usage:
none - Author: IETF GNAP Working Group (
txauth@ietf.org) - Change Controller:
IETF
10.2.4. application/gnap-binding-rotation-jws 🌸
this one is the sibling 👯♀️ same vibe, but this time the GNAP token rotation message gets bound with an attached JWS mechanism instead. so the signature rides along inside the message itself, no cap 🎀
- Type name:
application - Subtype name:
gnap-binding-rotation-jws - Required parameters:
N/A - Optional parameters:
N/A - Encoding considerations:
binary - Security considerations: See Section 11 of RFC 9635. 🙈 yes, again, go read it, it's serious
- Interoperability considerations:
N/A - Published specification:
RFC 9635 - Applications that use this media type:
GNAP - Fragment identifier considerations:
N/A - Additional information:
- Deprecated alias names for this type:
N/A - Magic number(s):
N/A - File extension(s):
N/A - Macintosh file type code(s):
N/A
- Deprecated alias names for this type:
- Person & email address to contact for further information: IETF GNAP Working Group (
txauth@ietf.org) - Intended usage:
COMMON - Restrictions on usage:
none - Author: IETF GNAP Working Group (
txauth@ietf.org) - Change Controller:
IETF
10.3. GNAP Grant Request Parameters 🧚
okay this is where IANA gets a brand new registry for GNAP grant request parameters 📋 it's called "GNAP Grant Request Parameters" and this document is the one that defines it. the initial values are in Section 10.3.2, and any future new ones or changes to existing ones go through the Specification Required registration policy [RFC8126]. very organized, very slay 💖
The designated expert (DE) is expected to make sure of the following:
- All registrations follow the template presented in Section 10.3.1.
- The request parameter's definition is sufficiently orthogonal to existing functionality provided by existing parameters.
- Registrations for the same name with different types are sufficiently close in functionality so as not to cause confusion for developers.
- The request parameter's definition specifies the expected behavior of the AS in response to the request parameter for each potential state of the grant request.
10.3.1. Registration Template 📝
- Name: An identifier for the parameter.
- Type: The JSON type allowed for the value.
- Reference: Reference to one or more documents that specify 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.
10.3.2. Initial Contents 🍓
+==============+==================+===========================+
| Name | Type | Reference |
+==============+==================+===========================+
| access_token | object | Section 2.1.1 of RFC 9635 |
+--------------+------------------+---------------------------+
| access_token | array of objects | Section 2.1.2 of RFC 9635 |
+--------------+------------------+---------------------------+
| subject | object | Section 2.2 of RFC 9635 |
+--------------+------------------+---------------------------+
| client | object | Section 2.3 of RFC 9635 |
+--------------+------------------+---------------------------+
| client | string | Section 2.3.1 of RFC 9635 |
+--------------+------------------+---------------------------+
| user | object | Section 2.4 of RFC 9635 |
+--------------+------------------+---------------------------+
| user | string | Section 2.4.1 of RFC 9635 |
+--------------+------------------+---------------------------+
| interact | object | Section 2.5 of RFC 9635 |
+--------------+------------------+---------------------------+
| interact_ref | string | Section 5.1 of RFC 9635 |
+--------------+------------------+---------------------------+
Table 1
10.4. GNAP Access Token Flags 🔥
this is the registry for GNAP access token flags 🚩 it's called "GNAP Access Token Flags", the starting values live in Section 10.4.2, and new or changed entries go through the Specification Required registration policy [RFC8126]. same energy as before, bestie 💅
The DE is expected to make sure of the following:
- All registrations follow the template presented in Section 10.4.1.
- The flag specifies whether it applies to requests for tokens to the AS, responses with tokens from the AS, or both.
10.4.1. Registration Template 🌈
- Name: An identifier for the parameter.
- Allowed Use: Where the flag is allowed to occur. Possible values are
"Request","Response", and"Request, Response". - Reference: Reference to one or more documents that specify 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.
10.4.2. Initial Contents 💜
+=========+===================+====================+
| Name | Allowed Use | Reference |
+=========+===================+====================+
| bearer | Request, Response | Sections 2.1.1 and |
| | | 3.2.1 of RFC 9635 |
+---------+-------------------+--------------------+
| durable | Response | Section 3.2.1 of |
| | | RFC 9635 |
+---------+-------------------+--------------------+
Table 2
10.5. GNAP Subject Information Request Fields 👑
this document gives a way to request subject information from the AS to the client instance, and IANA has made a new registry for it called "GNAP Subject Information Request Fields" ✨ the initial values are in Section 10.5.2, and any future changes go through the Specification Required registration policy [RFC8126].
The DE is expected to make sure of the following:
- All registrations follow the template presented in Section 10.5.1.
- Registrations for the same name with different types are sufficiently close in functionality so as not to cause confusion for developers.
10.5.1. Registration Template 🎀
- Name: An identifier for the parameter.
- Type: The JSON type allowed for the value.
- Reference: Reference to one or more documents that specify 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.
10.5.2. Initial Contents 🦋
+===================+==================+=========================+
| Name | Type | Reference |
+===================+==================+=========================+
| sub_id_formats | array of strings | Section 2.2 of RFC 9635 |
+-------------------+------------------+-------------------------+
| assertion_formats | array of strings | Section 2.2 of RFC 9635 |
+-------------------+------------------+-------------------------+
| sub_ids | array of objects | Section 2.2 of RFC 9635 |
+-------------------+------------------+-------------------------+
Table 3
10.6. GNAP Assertion Formats 🫶
this document also defines a way to pass identity assertions between the AS and client instance 🪪 IANA made a registry for that called "GNAP Assertion Formats". The starting values are in Section 10.6.2, and future assignments and changes go through the Specification Required registration policy [RFC8126].
The DE is expected to make sure of the following:
- All registrations follow the template presented in Section 10.6.1.
- The definition specifies the serialization format of the assertion value as used within GNAP.
10.6.1. Registration Template 🌸
- Name: An identifier for the assertion format.
- Reference: Reference to one or more documents that specify 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.
10.6.2. Initial Contents 🧚♀️
+==========+===========================+
| Name | Reference |
+==========+===========================+
| id_token | Section 3.4.1 of RFC 9635 |
+----------+---------------------------+
| saml2 | Section 3.4.1 of RFC 9635 |
+----------+---------------------------+
Table 4
10.7. GNAP Client Instance Fields 💅
last registry in this batch 🥳 this one lets you send information about the client instance, and it's called "GNAP Client Instance Fields". The initial values are in Section 10.7.2, and changes go through the Specification Required registration policy [RFC8126]. we're almost at the finish line, I'm so proud of us 😭🔥
The DE is expected to make sure of the following:
- All registrations follow the template presented in Section 10.7.1.
- Registrations for the same name with different types are sufficiently close in functionality so as not to cause confusion for developers.
10.7.1. Registration Template 💖
- Name: An identifier for the parameter.
- Type: The JSON type allowed for the value.
- Reference: Reference to one or more documents that specify 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.
10.7.2. Initial Contents ⭐
+==========+========+===========================+
| Name | Type | Reference |
+==========+========+===========================+
| key | object | Section 7.1 of RFC 9635 |
+----------+--------+---------------------------+
| key | string | Section 7.1.1 of RFC 9635 |
+----------+--------+---------------------------+
| class_id | string | Section 2.3 of RFC 9635 |
+----------+--------+---------------------------+
| display | object | Section 2.3.2 of RFC 9635 |
+----------+--------+---------------------------+
Table 5
10.8. GNAP Client Instance Display Fields 💖
okay so this bit is all about sending end-user-facing info about the client instance 🎀 IANA made a brand new registry for it called "GNAP Client Instance Display Fields" 🦋 The initial values live in Section 10.8.2. Future adds and changes go through the Specification Required registration policy [RFC8126] ✨
The DE is expected to ensure the following:
- All registrations follow the template presented in Section 10.8.1. 💅
- Registrations for the same name with different types are sufficiently close in functionality so as not to cause confusion for developers. 🫶
10.8.1. Registration Template 🌸
- Name: An identifier for the parameter.
- Type: The JSON type allowed for the value.
- Reference: Reference to one or more documents that specify 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.
10.8.2. Initial Contents 🧚
+==========+========+===========================+
| Name | Type | Reference |
+==========+========+===========================+
| name | string | Section 2.3.2 of RFC 9635 |
+----------+--------+---------------------------+
| uri | string | Section 2.3.2 of RFC 9635 |
+----------+--------+---------------------------+
| logo_uri | string | Section 2.3.2 of RFC 9635 |
+----------+--------+---------------------------+
Table 6
10.9. GNAP Interaction Start Modes 🚀
this part is where the client instance kicks off the interaction between the end user and the AS 🙈 IANA made a new registry for it called "GNAP Interaction Start Modes" 💜 The starting values are in Section 10.9.2, and future assignments and changes go through the Specification Required registration policy [RFC8126] 🌈
The DE is expected to ensure the following:
- All registrations follow the template presented in Section 10.9.1. 💅
- Registrations for the same name with different types are sufficiently close in functionality so as not to cause confusion for developers. 🫶
- Any registration using an "object" type declares all additional parameters, their optionality, and their purpose. 📝
- The start mode clearly defines what actions the client is expected to take to begin interaction, what the expected user experience is, and any security considerations for this communication from either party. 🔐
- The start mode documents incompatibilities with other start modes or finish methods, if applicable. ⚠️
- The start mode provides enough information to uniquely identify the grant request during the interaction. For example, in the redirect and app modes, this is done using a unique URI (including its parameters). In the user_code and user_code_uri modes, this is done using the value of the user code. 🎯
10.9.1. Registration Template 🌸
- Mode: An identifier for the interaction start mode.
- Type: The JSON type for the value, either "string" or "object", as described in Section 2.5.1.
- Reference: Reference to one or more documents that specify 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.
10.9.2. Initial Contents 🧚
+===============+========+=============================+
| Mode | Type | Reference |
+===============+========+=============================+
| redirect | string | Section 2.5.1.1 of RFC 9635 |
+---------------+--------+-----------------------------+
| app | string | Section 2.5.1.2 of RFC 9635 |
+---------------+--------+-----------------------------+
| user_code | string | Section 2.5.1.3 of RFC 9635 |
+---------------+--------+-----------------------------+
| user_code_uri | string | Section 2.5.1.4 of RFC 9635 |
+---------------+--------+-----------------------------+
Table 7
10.10. GNAP Interaction Finish Methods 🏁
this is how the client instance gets told that the interaction between the end user and the AS is over 🎉 IANA made a new registry for it called "GNAP Interaction Finish Methods" 🦋 The starting values are in Section 10.10.2, and future assignments and changes go through the Specification Required registration policy [RFC8126] 💖
The DE is expected to ensure the following:
- All registrations follow the template presented in Section 10.10.1. 💅
- All finish methods clearly define what actions the AS is expected to take, what listening methods the client instance needs to enable, and any security considerations for this communication from either party. 🔐
- All finish methods document incompatibilities with any start modes, if applicable. ⚠️
10.10.1. Registration Template 🌸
- Method: An identifier for the interaction finish method.
- Reference: Reference to one or more documents that specify 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.
10.10.2. Initial Contents 🧚
+==========+=============================+
| Method | Reference |
+==========+=============================+
| redirect | Section 2.5.2.1 of RFC 9635 |
+----------+-----------------------------+
| push | Section 2.5.2.2 of RFC 9635 |
+----------+-----------------------------+
Table 8
10.11. GNAP Interaction Hints 💡
this section defines a set of hints the client instance can send to the AS to make the end-user interaction smoother 🫧 IANA made a registry for them called "GNAP Interaction Hints" 🌸 The starting values are in Section 10.11.2, and future assignments and changes go through the Specification Required registration policy [RFC8126] ✨
The DE is expected to ensure the following:
- All registrations follow the template presented in Section 10.11.1. 💅
- All interaction hints clearly document the expected behaviors of the AS in response to the hint, and an AS not processing the hint does not impede the operation of the AS or client instance. 🫶
10.11.1. Registration Template 🌸
- Name: An identifier for the parameter.
- Reference: Reference to one or more documents that specify 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.
10.11.2. Initial Contents 🧚
+============+===========================+
| Name | Reference |
+============+===========================+
| ui_locales | Section 2.5.3 of RFC 9635 |
+------------+---------------------------+
Table 9
10.12. GNAP Grant Response Parameters 🎁
this one is for the grant response itself 👑 IANA made a new registry for it called "GNAP Grant Response Parameters" ✨ The starting values are in Section 10.12.2, and future assignments and changes go through the Specification Required registration policy [RFC8126] 🌈
The DE is expected to ensure the following:
- All registrations follow the template presented in Section 10.12.1. 💅
- The response parameter's definition is sufficiently orthogonal to existing functionality provided by existing parameters. 🧩
- Registrations for the same name with different types are sufficiently close in functionality so as not to cause confusion for developers. 🫶
- The response parameter's definition specifies grant states for which the client instance can expect this parameter to appear in a response message. 🎯
10.12.1. Registration Template 🌸
- Name: An identifier for the parameter.
- Type: The JSON type allowed for the value.
- Reference: Reference to one or more documents that specify 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.
10.12.2. Initial Contents 🧚
+==============+==================+===========================+
| Name | Type | Reference |
+==============+==================+===========================+
| continue | object | Section 3.1 of RFC 9635 |
+--------------+------------------+---------------------------+
| access_token | object | Section 3.2.1 of RFC 9635 |
+--------------+------------------+---------------------------+
| access_token | array of objects | Section 3.2.2 of RFC 9635 |
+--------------+------------------+---------------------------+
| interact | object | Section 3.3 of RFC 9635 |
+--------------+------------------+---------------------------+
| subject | object | Section 3.4 of RFC 9635 |
+--------------+------------------+---------------------------+
| instance_id | string | Section 3.5 of RFC 9635 |
+--------------+------------------+---------------------------+
| error | object | Section 3.6 of RFC 9635 |
+--------------+------------------+---------------------------+
Table 10
10.13. GNAP Interaction Mode Responses 📬
this is the info the AS sends back so the client instance has what it needs to finish a particular interaction mode 🦋 IANA made a registry for it called "GNAP Interaction Mode Responses" 💖 The starting values are in Section 10.13.2, and future assignments and changes go through the Specification Required registration policy [RFC8126] ✨
The DE is expected to ensure the following:
- All registrations follow the template presented in Section 10.13.1. 💅
- If the name of the registration matches the name of an interaction start mode, the response parameter is unambiguously associated with the interaction start mode of the same name. 🎯
10.13.1. Registration Template 🌸
- Name: An identifier for the parameter.
- Reference: Reference to one or more documents that specify 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.
10.13.2. Initial Contents ✨
okay so here's the starter pack for the interaction mode responses registry 💅 every single entry points back to Section 3.3 of RFC 9635, so it's all one cohesive vibe 🫶
+===============+=========================+
| Name | Reference |
+===============+=========================+
| redirect | Section 3.3 of RFC 9635 |
+---------------+-------------------------+
| app | Section 3.3 of RFC 9635 |
+---------------+-------------------------+
| user_code | Section 3.3 of RFC 9635 |
+---------------+-------------------------+
| user_code_uri | Section 3.3 of RFC 9635 |
+---------------+-------------------------+
| finish | Section 3.3 of RFC 9635 |
+---------------+-------------------------+
| expires_in | Section 3.3 of RFC 9635 |
+---------------+-------------------------+
Table 11
10.14. GNAP Subject Information Response Fields 🦋
okay this one is so cute 🥹 this document gives the AS a way to send subject information back to the client instance, and IANA made a brand new registry called "GNAP Subject Information Response Fields" to keep track of it all 🎀 the starter values live in Section 10.14.2. future adds and changes go through the Specification Required registration policy [RFC8126] ✨
the DE is supposed to make sure of a couple things 👑
- All registrations follow the template presented in Section 10.14.1.
- Registrations for the same name with different types are close enough in functionality that nobody gets confused 🧚
10.14.1. Registration Template 🌸
Name: An identifier for the parameter.
Type: The JSON type allowed for the value.
Reference: Reference to one or more documents that specify the value, ideally including a URI where you can grab a copy of the document(s). Pointing at the relevant sections is also nice, but it's not required 💖
10.14.2. Initial Contents 🍓
+============+==================+=========================+
| Name | Type | Reference |
+============+==================+=========================+
| sub_ids | array of objects | Section 3.4 of RFC 9635 |
+------------+------------------+-------------------------+
| assertions | array of objects | Section 3.4 of RFC 9635 |
+------------+------------------+-------------------------+
| updated_at | string | Section 3.4 of RFC 9635 |
+------------+------------------+-------------------------+
Table 12
10.15. GNAP Error Codes 💀
this is the registry for all the errors the AS can throw at the client instance 😭 IANA set up a new one called "GNAP Error Codes", and the starter values are in Section 10.15.2. changes and new additions go through the Specification Required registration policy [RFC8126] 🫠
the DE is expected to ensure the following, bestie:
- All registrations follow the template presented in Section 10.15.1.
- The error response is sufficiently unique from other errors to provide actionable information to the client instance 🔥
- The definition of the error response specifies all conditions in which the error response is returned and the client instance's expected action.
10.15.1. Registration Template 🌈
Error: A unique string code for the error.
Reference: Reference to one or more documents that specify the value, ideally including a URI where you can grab a copy of the document(s). Pointing at the relevant sections is also nice, but it's not required 💖
10.15.2. Initial Contents ✨
+============================+=========================+
| Error | Reference |
+============================+=========================+
| invalid_request | Section 3.6 of RFC 9635 |
+----------------------------+-------------------------+
| invalid_client | Section 3.6 of RFC 9635 |
+----------------------------+-------------------------+
| invalid_interaction | Section 3.6 of RFC 9635 |
+----------------------------+-------------------------+
| invalid_flag | Section 3.6 of RFC 9635 |
+----------------------------+-------------------------+
| invalid_rotation | Section 3.6 of RFC 9635 |
+----------------------------+-------------------------+
| key_rotation_not_supported | Section 3.6 of RFC 9635 |
+----------------------------+-------------------------+
| invalid_continuation | Section 3.6 of RFC 9635 |
+----------------------------+-------------------------+
| user_denied | Section 3.6 of RFC 9635 |
+----------------------------+-------------------------+
| request_denied | Section 3.6 of RFC 9635 |
+----------------------------+-------------------------+
| unknown_user | Section 3.6 of RFC 9635 |
+----------------------------+-------------------------+
| unknown_interaction | Section 3.6 of RFC 9635 |
+----------------------------+-------------------------+
| too_fast | Section 3.6 of RFC 9635 |
+----------------------------+-------------------------+
| too_many_attempts | Section 3.6 of RFC 9635 |
+----------------------------+-------------------------+
Table 13
10.16. GNAP Key Proofing Methods 🔐
this is where the methods live for how the client instance proves it actually owns a key 🗝️ IANA made a new registry called "GNAP Key Proofing Methods", with starter values in Section 10.16.2. same deal as before: future assignments and changes go through the Specification Required registration policy [RFC8126] 💅
the DE is expected to ensure the following:
- All registrations follow the template presented in Section 10.16.1.
- Registrations for the same name with different types are close enough in functionality that nobody gets confused 🧚
- The proofing method provides sufficient coverage of and binding to the protocol messages to which it is applied.
- The proofing method definition clearly enumerates how all requirements in Section 7.3 are fulfilled by the definition 🎀
10.16.1. Registration Template 🌸
Method: A unique string code for the key proofing method.
Type: The JSON type allowed for the value.
Reference: Reference to one or more documents that specify the value, ideally including a URI where you can grab a copy of the document(s). Pointing at the relevant sections is also nice, but it's not required 💖
10.16.2. Initial Contents 🍓
+=========+========+===========================+
| Method | Type | Reference |
+=========+========+===========================+
| httpsig | string | Section 7.3.1 of RFC 9635 |
+---------+--------+---------------------------+
| httpsig | object | Section 7.3.1 of RFC 9635 |
+---------+--------+---------------------------+
| mtls | string | Section 7.3.2 of RFC 9635 |
+---------+--------+---------------------------+
| jwsd | string | Section 7.3.3 of RFC 9635 |
+---------+--------+---------------------------+
| jws | string | Section 7.3.4 of RFC 9635 |
+---------+--------+---------------------------+
Table 14
10.17. GNAP Key Formats 🌈
this one defines the formats a public key value can come in 🔑 IANA set up a registry titled "GNAP Key Formats", and its starter values are in Section 10.17.2. future assignments and modifications go through the Specification Required registration policy [RFC8126] ✨
the DE is expected to ensure the following:
- All registrations follow the template presented in Section 10.17.1.
- The key format specifies the structure and serialization of the key material 🫶
10.17.1. Registration Template 🌸
Format: A unique string code for the key format.
Reference: Reference to one or more documents that specify the value, ideally including a URI where you can grab a copy of the document(s). Pointing at the relevant sections is also nice, but it's not required 💖
10.17.2. Initial Contents 🦋
+===========+=========================+
| Format | Reference |
+===========+=========================+
| jwk | Section 7.1 of RFC 9635 |
+-----------+-------------------------+
| cert | Section 7.1 of RFC 9635 |
+-----------+-------------------------+
| cert#S256 | Section 7.1 of RFC 9635 |
+-----------+-------------------------+
Table 15
10.18. GNAP Authorization Server Discovery Fields ⭐
last one in this batch! this document describes a discovery document for an AS, and IANA created a registry called "GNAP Authorization Server Discovery Fields" for it 🧚 the starter values are in Section 10.18.2, and future changes go through the Specification Required registration policy [RFC8126] 💜
the DE is expected to ensure the following:
- All registrations follow the template presented in Section 10.18.1.
- Registrations for the same name with different types are close enough in functionality that nobody gets confused 🧚
- The values in the discovery document are sufficient to provide optimization and hints to the client instance, but knowledge of the discovered value is not required for starting a transaction with the AS. ✨
10.18.1. Registration Template 🌈
Name: An identifier for the parameter.
Type: The JSON type allowed for the value.
Reference: Reference to one or more documents that specify the value, ideally including a URI where you can grab a copy of the document(s). Pointing at the relevant sections is also nice, but it's not required 💖
10.18.2. Initial Contents ✨
Bestie, here's the starter pack for the GNAP Authorization Server Discovery Fields registry 💅 Every field has a name, a type, and a reference back to Section 9 of RFC 9635 🎀
+======================================+==========+=============+
| Name | Type | Reference |
+======================================+==========+=============+
| grant_request_endpoint | string | Section 9 |
| | | of RFC 9635 |
+--------------------------------------+----------+-------------+
| interaction_start_modes_supported | array of | Section 9 |
| | strings | of RFC 9635 |
+--------------------------------------+----------+-------------+
| interaction_finish_methods_supported | array of | Section 9 |
| | strings | of RFC 9635 |
+--------------------------------------+----------+-------------+
| key_proofs_supported | array of | Section 9 |
| | strings | of RFC 9635 |
+--------------------------------------+----------+-------------+
| sub_id_formats_supported | array of | Section 9 |
| | strings | of RFC 9635 |
+--------------------------------------+----------+-------------+
| assertion_formats_supported | array of | Section 9 |
| | strings | of RFC 9635 |
+--------------------------------------+----------+-------------+
| key_rotation_supported | boolean | Section 9 |
| | | of RFC 9635 |
+--------------------------------------+----------+-------------+
Table 16
11. Security Considerations 🔐
Okay, this is the part where things get SERIOUS 😭 Beyond the normative requirements in this document, implementors are strongly encouraged to think through these extra security considerations when they build and deploy GNAP. Don't skip this bit, it's the tea 🫶
11.1. TLS Protection in Transit 🔒
Every GNAP request sent over an untrusted network connection has to go over TLS, as outlined in [BCP195], so that nobody in the middle can mess with or spy on the contents of the request and response 🙈 That covers every request from a client instance to the AS, every request from the client instance to an RS, and any requests back to a client instance, like the push-based interaction finish method. Also, requests between a browser and other components, like during redirect-based interaction, need TLS too, or equivalent protection such as a network connection local to the browser ("localhost") 💜
Even though requests from the client instance to the AS are signed, the signature alone does not stop an attacker from intercepting the request 💀 TLS protects the response too, so an attacker can't snoop on the info coming back. This matters a LOT in this spec for security artifacts like nonces, and for personal info like subject information 😭
Using key-bound access tokens does not get you off the hook for TLS on calls to the RS 🙅♀️ The keys and signatures tied to a bound access token stop an attacker from using a stolen token, sure. But without TLS, an attacker could still watch the data going to the RS and coming back from it during legit use of the client instance under attack. Without TLS, an attacker could also profile the calls between the client instance and the RS, and learn how the API between the client software and RS software works, stuff they'd otherwise never know 👀
Heads up: connections from the end user and the RO's browser need TLS as well. That applies during the initial redirects to an AS's components during interaction, during any interaction with the RO, and during any redirect back to the client instance. Without TLS on these parts of the flow, an attacker could wait for a valid request to start and then take over the RO's interaction session 🚨
11.2. Signing Requests from the Client Software ✍️
Even though all GNAP requests have to be sent over TLS or its equivalent, TLS alone isn't enough to protect every part of a multi-party, multi-stage protocol like GNAP 🧩 TLS also isn't built to tie multiple requests to each other over time. So GNAP uses message-level protection and key presentation mechanisms that strongly associate a request with a key held by the client instance (see Section 7) 🔑
On the initial request from a client instance to the AS, the client instance has to identify itself and prove it holds a cryptographic key 🦋 If the AS already knows the key, e.g., it was previously registered or can be dereferenced to a trusted source, the AS can attach a set of policies to the client instance identified by that key. Without the client instance proving it holds that key, the AS couldn't trust that the connection came from any particular client, and couldn't apply any of those policies 🙃
Even more important, proving possession of a key on the first request lets the AS link future requests together by binding every future request in that transaction to the same key 🎀 The access token used for grant continuation is bound to the same key and proofing mechanism the client instance used in its initial request. So the client instance has to prove possession of that same key in future requests, and that's how the AS can be sure the same client instance is running the follow-ups for an ongoing grant request 👑 That means the AS has to make sure all subsequent requests for a grant are associated with either the same key that started the grant, or the most recent rotation of that key. This still holds even if the initial key was previously unknown to the AS, like when a client instance makes an ephemeral key for its request ✨ Without this ongoing association, an attacker could impersonate a client instance in the middle of a grant request, and potentially steal access tokens and subject information with impunity 😱
Also, all access tokens in GNAP are associated by default with the key that was presented during the grant request that created the token 🍓 That association lets an RS know that whoever presents the access token is the same party it was issued to, as identified by their keys. Non-bound bearer tokens are an option in GNAP, but they come with their own trade-offs, which are discussed in Section 11.9 💅
TLS works at the transport layer and makes sure only the parties on either end of a connection can read what's passed along it 🌈 Every time a new connection is made, like for a new HTTP request, a new trust relationship that's mostly unrelated to previous connections is set up. Modern TLS does use session resumption, but that still needs to be paired with authentication methods that figure out who the parties on the connections are 🤷♀️ In other words, TLS alone can't tell you that the same party is making a set of calls over time, since each new TLS connection means both the client and the server (or just the server, when using MTLS (Section 7.3.2)) have to validate the other party's identity again. Methods described in [RFC9525] can do that kind of verification, but in lots of cases they aren't enough to establish the identity of the client instance 🫠
To deal with this, GNAP defines a set of key binding methods in Section 7.3 that allow authentication and proof of possession by the caller, which is usually the client instance 🧚 These methods are meant to be used alongside TLS on all connections.
11.3. MTLS Message Integrity 🔏
The MTLS key proofing mechanism (Section 7.3.2) lets a client instance present a key using a certificate at the TLS layer ✨ Since TLS protects the entire HTTP message in transit, checking the TLS client certificate presented with the message gives a sufficient binding between the two. BUT, since TLS works at a separate layer from HTTP, there's no direct connection between the TLS key presentation and the message itself, other than the fact that the message came over that TLS channel 🙈 In other words, any HTTP message can be sent over the TLS channel in question with the same level of trust. The verifier is responsible for making sure the key in the TLS client certificate is the one expected for a particular request. For example, if the request is a grant request (Section 2), the AS needs to compare the TLS client certificate presented at the TLS layer to the key identified in the request content itself, either by value or through a referenced identifier 🔍
It gets messier with the TLS terminating reverse proxy (TTRP) pattern, which is super common in deployments 🌀 In this pattern, the TTRP validates the TLS connection, then forwards the HTTP message contents on to an internal system for processing. The system processing the HTTP message no longer has access to the original TLS connection's info and context 😭 To make up for this, the TTRP could inject the TLS client certificate into the forwarded request using the HTTP Client-Cert header field [RFC9111], so the downstream system can access the certificate information 🎀 The TTRP has to be trusted to provide accurate certificate information, and the connection between the TTRP and the downstream system also has to be protected 🔒 The TTRP could add some extra assurance, for example by adding its own signature to the Client-Cert header field using HTTP message signatures [RFC9421]. GNAP would effectively ignore this signature (since it wouldn't use GNAP's tag parameter value), but the downstream service would understand it as part of its deployment 🦋
Additional considerations for different deployment patterns and key distribution mechanisms for MTLS are found in Section 11.4 💖
11.4. MTLS Deployment Patterns 🎀
okay so bestie, GNAP doesn't say how a client instance's keys get known to the AS ahead of time 🙈 that's a whole separate job! 💅 but the Public Key Infrastructure (PKI) can handle managing the keys client instances use when they call the AS 🔑 the AS can trust a root key from a trusted authority and just vibe from there ✨ this is especially relevant to the MTLS key proofing method, where the client instance shows its certificate to the AS as part of the TLS connection 🫶
so an AS using PKI to validate the MTLS connection needs to make sure the certificate it was shown was issued by a trusted certificate authority before it lets the connection continue 🔥 the best part? PKI-based certificates let a key get revoked and rotated through management at the certificate authority, so the AS doesn't need extra registration or management 😭 it's giving low-maintenance. ngl, PKI for mutually authenticated TLS has historically been kinda hard to deploy, especially at scale 💀 but it's still a totally valid choice for systems where that management overhead isn't a dealbreaker 🌸
MTLS in GNAP doesn't have to use a PKI backing though 🦋 self-signed certificates and certificates from untrusted authorities can still be presented as part of a TLS connection. in that case the verifier validates the connection but accepts whatever certificate the client software showed 🙈 that certificate can then be bound to all future connections from that client software by being bound to the resulting access tokens, which is a trust-on-first-use pattern 🧚 for more considerations on MTLS as a key proofing mechanism, see Section 11.3 💜
11.5. Protection of Client Instance Key Material 🔐
okay so client instances are identified by their unique keys, and anyone with access to a client instance's key material can impersonate that client instance to all parties 😭 this is true for calls to the AS and also for calls to an RS using an access token bound to the client instance's unique key. so protecting its private key material is of utmost importance for a client instance 🫶 literally the most important thing!!
different types of client software have different ways of creating, managing, and registering keys 🌈 GNAP explicitly allows ephemeral clients, like single-page applications (SPAs) and single-user clients (such as mobile applications), to create and present their own keys during the initial grant request without any explicit pre-registration step ✨ the client software can securely generate a key pair on the device and present the public key, along with proof of holding the matching private key, to the AS as part of the initial request. to help the AS trust these ephemeral keys, GNAP also lets an extensible set of client information get passed with the request 💖 that info can include device posture and third-party attestations of the client software's provenance and authenticity, depending on the needs and capabilities of the client software and its deployment 🍓
from GNAP's perspective, each distinct key is a different client instance 🦋 but multiple client instances can be grouped together by an AS policy and treated similarly to each other. for instance, if an AS knows of several different keys for different servers in a cluster, it can decide that authorizing one of those servers applies to all the other servers in the cluster 👑 an AS that does this needs to be super careful about how it groups client keys together in its policy, since a breach of one instance would have direct effects on the others in the cluster 😭💀
also, if an end user controls multiple instances of a single type of client software, like an app installed on multiple devices, each instance is expected to have a separate key and be issued separate access tokens 📱 but if the AS can group these instances together as described above, it can streamline authorization for new instances of the same client software 💅 for example, if two client instances can present proof of a valid installation of a piece of client software, the AS can associate the approval of the first instance with all the related instances. the AS could then choose to skip an explicit prompt of the RO for approval during authorization, since that approval has already been given ✨ an AS doing this would need to take assurance measures that the different instances really are correlated and authentic, and also make sure the expected RO is in control of the client instance 🫶
finally, if multiple instances of client software each have the same key, then from GNAP's perspective they are functionally the same client instance, because GNAP has no reasonable way to tell them apart 🙈 this could happen if multiple instances within a cluster can securely share secret information among themselves. even though there are multiple copies of the software, the shared key makes them all present as a single instance 🧚 it is considered bad practice to share keys between copies of software unless they are very tightly integrated and can be closely managed 🚫 it is particularly bad practice to let an end user copy keys between client instances and willingly use the same key in multiple instances 💀🔥
11.6. Protection of Authorization Server 🛡️
okay so the AS does critical work in GNAP 👑 it authenticates client software, manages interactions with end users to gather consent and give notice, and issues access tokens for client instances to present to RSs. so protecting the AS is central to any GNAP deployment, no cap 💜
if an attacker gets control over an AS, they could create fraudulent tokens and manipulate registration info to allow malicious clients 😭 those tokens and clients would then be trusted by other components in the ecosystem, because they're under the AS's protection 💀
if the AS uses signed access tokens, an attacker who controls the AS's signing keys could manufacture fraudulent tokens for use at RSs under the AS's protection 🔥
if an attacker can impersonate an AS, they could trick legitimate client instances into making signed requests for information that could get proxied to a real AS 🙈 to stop this, all communications to the AS need to be made over TLS or its equivalent, and the software making the connection has to validate the certificate chain of the host it's connecting to 🔐
so protecting, monitoring, and auditing the AS is paramount to keeping a GNAP-protected ecosystem secure 🚨 the AS is a valuable target for attackers. but here's the iconic part: the core focus and function of the AS is to provide security for the ecosystem 💅 unlike the RS, whose focus is to provide an API, or the client software, whose focus is to access that API 🌸
11.7. Symmetric and Asymmetric Client Instance Keys 🗝️
okay so many of GNAP's cryptographic methods for key proofing can support both asymmetric and symmetric cryptography, and they can be extended to use a wide variety of mechanisms 🦋 implementors will find the guidelines on cryptographic key management in [RFC4107] useful 📚 symmetric cryptography has some benefits in speed and simplicity 🌈 but it has a distinct drawback: both parties need access to the same key to do both signing and verification of the message 😬 when more than two parties share the same symmetric key, data origin authentication is not provided. any party that knows the symmetric key can compute a valid MAC, so the contents could have originated from any one of the parties 💀
using symmetric cryptography means that when the client instance calls the AS to request a token, the AS needs to know the exact value of the client instance's key (or be able to derive it) so it can validate the key proof signature 🔑 with asymmetric keys, the client only needs to send its public key to the AS so the AS can verify the client holds the matching private key, whether or not that key was pre-registered with the AS ✨
symmetric keys also have the expected advantage of better protection against quantum threats in the future 🔮 plus these types of keys (and their secure derivations) are widely supported across many cloud-based key management systems ☁️
when used to bind to an access token, a key value must be known by the RS in order to validate the proof signature on the request 🫶 common ways of communicating these proofing keys include putting info in a structured access token and letting the RS look up the associated key material using the value of the access token. with symmetric cryptography, both of these methods would expose the signing key to the RS and, for a structured access token, potentially to any party that can see the access token itself, unless the token's payload has been encrypted 😱 any of those parties could then make calls using the access token by creating a valid signature with the shared key 💀 with asymmetric cryptography, the RS only needs to know the public key associated with the token to validate the request, so the RS cannot create any new signed calls 🙌
both signing approaches are allowed, but GNAP treats these two classes of keys somewhat differently 🌸 only the public portion of asymmetric keys is allowed to be sent by value in requests to the AS when establishing a connection. since sending a symmetric key (or the private portion of an asymmetric key) would expose the signing material to any parties on the request path, including attackers, sending these kinds of keys by value is prohibited 🚫 symmetric keys can still be used by client instances, but only if the client instance sends a reference to the key and not its value 🧚 this lets the AS use pre-registered symmetric keys, as well as key derivation schemes, to get the benefits of symmetric cryptography without key distribution at runtime, which would expose the keys in transit 💖
both the AS and client software can use systems such as hardware security modules to strengthen key security storage and generation for both asymmetric and symmetric keys (see also Section 7.1.2) 🛡️✨
11.8. Generation of Access Tokens ✨
okay so the contents of an access token need to be set up so that only the AS that generated it could ever create one, and an attacker can't tweak them to get different or extra access rights 💅
one way to do this is a cryptographically random value as the token, generated by the AS with a secure randomization function that has plenty of entropy 🎲 the odds of an attacker guessing that output and landing on a valid access token are exceedingly small 🙈 and even if they did, they'd have zero control over what the token represents, because that info stays locked up with the AS 🔒
another way is a structured token that's cryptographically signed 🧾 the payload tells the RS what the token is good for, and the signature the AS applies during generation covers that payload. only the AS can make that signature, so only the AS can make the signed token 🖋️ the odds of an attacker guessing a signature value with a useful payload are exceedingly small too 🫣 but this technique only works if every targeted RS checks the signature of the access token ⚠️ any RS that doesn't validate the signature on all presented tokens would be susceptible to injection of a modified or falsified token 💀 and the AS has to carefully protect the keys it uses to sign access tokens 🔑 because anyone with access to those signing keys could create seemingly valid access tokens with them 😱
11.9. Bearer Access Tokens 🍓
bearer access tokens can be used by any party that has access to the token itself, with no additional info needed 🎟️ so, naturally, any RS a bearer token gets presented to has the technical capability to present that token to another RS, as long as it's still valid 😬 it also means any party that can capture the token value, whether in storage or in transit, can use the access token 👀 bearer tokens are inherently simpler, sure, but that simplicity has been misapplied and abused to build needlessly insecure systems 🙃 the downsides have gotten more pertinent lately, since stronger authentication systems have caused some attacks to shift toward targeting tokens and APIs 🎯
in GNAP, key-bound access tokens are the default because of their higher security properties 🔐 bearer tokens can be used in GNAP too, but their use should be limited to cases where the simplicity benefits outweigh the significant security downsides ⚖️ one common deployment pattern is a gateway that takes in key-bound tokens on the outside and verifies the signatures on the incoming requests, but translates those requests into a bearer token for trusted internal systems 🚪 the bearer tokens are never issued or available outside the internal systems, which greatly limits exposure of the less-secure tokens while still letting the internal deployment enjoy the perks of bearer tokens ✨
11.10. Key-Bound Access Tokens 🗝️
key-bound access tokens, as the name suggests, are bound to a specific key and have to be presented along with proof of that key during use 🔏 the key itself isn't presented at the same time as the token, so even if a token value gets captured, it can't be used to make a new request 🙅 this is especially true for an RS, which sees the token value but never sees the keys used to make the request (assuming asymmetric cryptography is in use, see Section 11.7) 🫶
key-bound tokens only provide this extra layer of protection when the RS checks the signature of the message presented with the token ✅ accepting an invalid presentation signature, or failing to check the signature entirely, would let an attacker make calls with a captured access token without having access to the related signing key material 😱
besides validating the signature of the presentation message itself, the RS also needs to make sure the signing key used is appropriate for the presented token 🧐 if the RS doesn't ensure the right keys were used to sign a message with a specific token, an attacker could capture an access token, sign the request with their own keys, and totally negate the benefits of key-bound access tokens 💀
the RS also needs to make sure sufficient portions of the message are covered by the signature 📦 anything outside the signature could still affect the API's processing decisions, but those items wouldn't be strongly bound to the token presentation. so an attacker could capture a valid request and then manipulate parts of it outside the signature envelope to cause unwanted actions at the protected API 😤
some key-bound tokens are susceptible to replay attacks, depending on the details of the signing method used 🔁 so key proofing mechanisms used with access tokens need to use replay-protection mechanisms covered under the signature, such as a per-message nonce, a reasonably short time validity window, or other uniqueness constraints 🛡️ the details of using these will vary depending on the key proofing mechanism in use. for example, HTTP message signatures have both a created and a nonce signature parameter, as well as the ability to cover significant portions of the HTTP message 🌐 all of these can be used to limit the attack surface 🎯
11.11. Exposure of End-User Credentials to Client Instance 🫣
as a delegation protocol, one of the main goals of GNAP is to keep the client software from being exposed to any credentials or information about the end user or RO as a requirement of the delegation process 🙌 by using the variety of interaction mechanisms, the RO can interact with the AS without ever authenticating to the client software, and without the client software having to impersonate the RO by replaying their credentials 🎭
consequently, no interaction methods defined in this specification require the end user to enter their credentials 🙅♀️ but it is technologically possible for an extension to be defined to carry such values. that would be dangerous, since it would allow rogue client software to directly collect, store, and replay the end user's credentials outside of any legitimate use within a GNAP request 🚨
the concerns of such an extension could be mitigated through use of a challenge and response unlocked by the end user's credentials 🔓 for example, the AS presents a challenge as part of an interaction start method, and the client instance signs that challenge using a key derived from a password presented by the end user 🔑 it would be possible for the client software to collect this password in a secure software enclave, without exposing the password to the rest of the client software or putting it across the wire to the AS 🏰 the AS can validate this challenge response against a known password for the identified end user ✅ while an approach such as this doesn't remove all of the concerns surrounding a password-based scheme, it's at least possible to implement in a more secure fashion than simply collecting and replaying the password 💪 even so, such schemes should only ever be used by trusted clients, due to the ease of abusing them 😬
11.12. Mixing Up Authorization Servers 🥸
if a client instance is able to work with multiple ASes simultaneously, it's possible for an attacker to add a compromised AS to the client instance's configuration and make the client software start a request at the compromised AS 🕵️ that AS could then proxy the client's request to a valid AS, trying to get the RO to approve access for the legit client instance 🎣
a client instance needs to always be aware of which AS it's talking to throughout a grant process, and make sure any callback for one AS doesn't get conflated with a callback to a different AS 🧠 the interaction finish hash calculation in Section 4.2.3 lets a client instance protect against this kind of substitution, but only if the client instance actually validates the hash 🧾 if the client instance doesn't use an interaction finish method or doesn't check the interaction finish hash value, the compromised AS can be granted a valid access token on behalf of the RO 😭 see Sections 4.5.5 and 5.5 of [AXELAND2021] for details of one such attack, which has been addressed in this document by including the grant endpoint in the interaction hash calculation 📚 note that the client instance still needs to validate the hash for the attack to be prevented 🫶
11.13. Processing of Client-Presented User Information 💁♀️
okay so GNAP lets the client instance send assertions and identifiers for the current user to the AS as part of the initial request. the AS should only ever treat this as a hint, bestie, because it has no way to know if the person is actually at the client software unless it uses an interaction mechanism. it doesn't guarantee the user is there 😬 but it IS a statement from the client software that the AS can take into account 🫶
for example, if a specific user is claimed to be present before interaction, but a different user shows up during interaction, the AS can either call that an error, or tell the client instance through the returned subject information that the current user has changed from what the client thought 👀 this user info can also make the interaction smoother when the user is actually there ✨ like, instead of making the user type their account identifier at a redirected URI, the AS can jump straight to asking for their account credentials 🔥 or if there's already an existing session that matches the identifier the client gave, the AS can figure that out and skip an explicit authentication event by the RO 💅
in cases where the AS trusts the client software more fully, because of policy or a previous approval of a given client instance, the AS can treat this user info as a statement that the user is present 🌸 and it could issue access tokens and release subject information without any interaction. the AS should only do this in very limited circumstances though, bestie 🙈 because a client instance could claim whatever it likes for the user's identifiers in its request. the AS can reduce that risk by issuing randomized opaque identifiers to client instances to represent different end-user accounts after an initial login 🧚
when a client instance presents an assertion to the AS, the AS needs to evaluate it 🧐 the AS is unlikely to be the intended audience of an assertion the client software is holding, so the AS will need to evaluate it in a different context. even so, the AS can still check that the assertion was generated by a trusted party, was appropriately signed, and is within any time validity windows it states 💜 if the client instance's audience identifier is known to the AS and can be linked to the key the client instance presents, the AS can also check that the right client instance is presenting the claimed assertion 🦋 all of this stops an attacker from presenting a manufactured assertion, or one captured from an untrusted system 🛡️ however, if the audience of the assertion isn't validated, a captured assertion could be presented by the client instance to impersonate a given end user 😭 in that case the assertion offers little more protection than a simple identifier would, which is not it lol.
there's also a special case where the AS is the one that generated the assertion the client instance is presenting ✨ in that situation the AS can validate that it really did issue the assertion, and that it's associated with the client instance presenting it 🫶
11.14. Client Instance Pre-registration 📝
each client instance is identified by its own unique key 🔑 and for some kinds of client software, like a web server or backend system, this identification can be made easier by registering a single key for a piece of client software ahead of time 🍓 the registration can also come with display attributes that get used during the authorization process to identify the client software to the user 💖 in these cases, it can be assumed that only one instance of the client software will exist, and it'll probably serve lots of different users 🌈
a client's registration record needs to include its identifying key 🗝️ and any clients using symmetric cryptography for key proofing mechanisms need to have their keys pre-registered, no exceptions 🙅♀️ the registration should also include anything that would help the authorization process, like a display name and logo ✨ the registration record can also limit a client to asking for certain kinds of information, or to using specific interaction mechanisms at runtime 💅
it also makes sense to pre-register client instances when the software is acting autonomously, without needing runtime approval from an RO or any interaction with an end user 🤖 in those cases the AS needs to rely on the trust decisions made before runtime to decide what rights and tokens to grant to a given client instance 🔥
BUT, it doesn't make sense to pre-register many types of clients 😬 single-page applications (SPAs) and mobile/desktop applications are the big problem children here 💀 for SPAs, the instances are ephemeral, so long-term registration of a single instance creates a lot of storage and management overhead at the AS 📦 for mobile apps, every installation of the client software is its own separate instance 📱 and sharing one key among all of them would be terrible for security, because if any single installation gets compromised, every copy for every user is compromised too 😱
an AS can treat these classes of client software differently from each other 🎀 for example, it might only allow access to certain high-value APIs for pre-registered known clients, or require an active end-user delegation of authority for any client software that isn't pre-registered 👑
an AS can also give ROs warnings and caveats during the authorization process, so the user can make an informed decision about the software they're authorizing 🧠 for example, if the AS has vetted the client software and this specific instance, it can show a different authorization screen than it would for a client instance that's presenting all of its info at runtime 🌟
finally, an AS can use platform attestations and other signals from the client instance at runtime to figure out whether the software making the request is legit 🕵️♀️ the details of those attestations are outside the scope of this specification 🙃 but the client portion of a grant request is a natural extension point for that kind of info, through the "GNAP Client Instance Fields" registry (Section 10.7) 🦋
11.15. Client Instance Impersonation 🎭
if client instances are allowed to set their own user-facing display info, like a display name and website URL, a malicious client instance could impersonate legit client software 😈 and trick users into authorizing the malicious client 🚩
requiring clients to pre-register doesn't fully fix this, since lots of pre-registration systems have self-service portals for managing client registration 💻 which lets authenticated developers type self-asserted info into the management portal 😬 that's a whole loophole, bestie 🙈
an AS can help by actively filtering all self-asserted values presented by client software 🧹 both dynamically as part of GNAP and through a registration portal, so there's less room for impersonation 🛡️
an AS can also warn the RO about where the information it's displaying came from 🕵️♀️ so the RO can make a more informed delegation decision 💅 for example, an AS can visually tell apart a client instance that can be traced back to a specific developer's registration from an instance that just self-asserted its own display info ✨
11.16. Client-Hosted Logo URI 🖼️
the logo_uri client display field defined in Section 2.3.2 lets the client instance point to a URI where an image can be fetched for display during authorization decisions 🌸 when that URI points to an externally hosted resource (as opposed to a data: URI), the logo_uri field brings extra challenges on top of the stuff in Section 11.15 😭
when a logo_uri is externally hosted, the client software (or whoever hosts the asset) can change the logo's contents without telling the AS 🫠 and since the logo counts as part of the client software's identity, that flexibility is useful for a more dynamically managed client instance that makes use of distributed systems 🌐
but the same flexibility means the host of the asset could change the hosted file in a malicious way 💀 like swapping the image for malicious software to download, or imitating a different piece of client software 👿 and fetching the URI could also accidentally leak info to the image host in the Referer HTTP header field, if one gets sent 🙈 even though GNAP intentionally avoids putting security parameters in front-channel URIs wherever possible, the AS still should take steps so this info doesn't leak by accident 🔐 for example, it could set a referrer policy on image links, or only display images on pages served from a URI with no sensitive security or identity info 🧼
to avoid all this, the AS can insist on data: URIs, though that might not be practical for every type of client software 🤷♀️ or the AS could pre-fetch the content of the URI and show the RO its own copy instead 📸 but this practice opens the AS up to potential SSRF attacks, as discussed in Section 11.34 ⚠️
11.17. Interception of Information in the Browser 🕸️
most info passed through the web browser can be intercepted and possibly manipulated by stuff inside the browser, like scripts loaded within pages 😩 info in the URI shows up in browser and server logs, and it can also leak to other parties through Referer HTTP headers 🫣
GNAP's design limits how much info gets passed directly through the browser ✨ so opaque URIs work in most circumstances 💜 for the redirect-based interaction finish mechanism, named query parameters carry unguessable opaque values 🔑 and for those, GNAP requires creating and validating a cryptographic hash 🧮 which protects the query parameters added to the URI and ties them to an ongoing grant process and to values that never get passed in the URI 🔗 the client instance has to properly validate that hash, so an attacker can't inject an interaction reference meant for a different AS or client instance 🚫
several interaction start mechanisms use URIs that the AS creates and passes to the client instance 🌐 those URIs are opaque to the client instance, but the AS could still accidentally include parameters, paths, or other pieces of info that leak security data or can be manipulated by someone in the middle of the transaction 😱 an AS implementation can dodge this by creating URIs with unguessable values that are randomized for each new grant request 🎲✨
11.18. Callback URI Manipulation ✨
Okay so the callback URI used in interaction finish mechanisms is defined by the client instance, bestie 🎀 The AS treats it as opaque, like a sealed envelope 💌, but it can still carry stuff the client needs for its own operations. The client instance can even tuck a state value in there so the callback request gets matched up with the ongoing grant request 🦋
Here's the tea 🫢: that URI is exposed to the end user's browser, so it can get logged or messed with in transit before the request ever reaches the client software 😭 That means a client instance should never put security-critical or private info into the callback URI in cleartext. Like, if the client software puts a post-redirect target URI into its callback URI to the AS, an attacker could manipulate that target and turn the client into an open redirector at the client 💀
So what's the move? The client instance can use an unguessable identifier in the URI instead, and then the client software looks up the details of the pending request with it 🔐 BUT this approach needs some statefulness from the client software during the redirect, so clients that can't hold state through a redirect should not use redirect-based interaction mechanisms 🙅♀️
11.19. Redirection Status Codes 🍓
As described in [OAUTH-SEC-TOPICS], a server should never use HTTP status code 307 (Temporary Redirect) to redirect a request that potentially contains user credentials. If an HTTP redirect is used for such a request, HTTP status code 303 (See Other) should be used instead.
Okay, the why 🧠 Status code 307 (Temporary Redirect), as defined in the HTTP standard [HTTP], requires the user agent to keep the method and content of a request, so the content of the POST request gets submitted to the redirect target again. In the HTTP standard [HTTP], only status code 303 (See Other) unambiguously makes the user agent rewrite the HTTP POST request into an HTTP GET request, which gets rid of the POST content in the redirected request ✨ For all other status codes, including status code 302 (Found), user agents are allowed to keep a redirected POST request as a POST, and so they can resubmit the content.
Now the scary part 😱 Using status code 307 (Temporary Redirect) creates a vulnerability when you use the redirect interaction finish method (Section 3.3.5). With this method, the AS might prompt the RO to enter their credentials in a form, and that form gets submitted back to the AS with an HTTP POST request. The AS checks the credentials and, if they're good, it may immediately redirect the RO to the client instance's redirect URI. Because of status code 307 (Temporary Redirect), the RO's user agent now sends the RO's credentials along to the client instance.
A malicious client instance can then use those credentials to impersonate the RO at the AS. 💀 This is why the 307 thing is a no-go for credential-carrying requests!
Another leak to watch out for 👀 Redirecting away from the initial URI in an interaction session could also leak information found in that initial URI through the HTTP Referer header field, since the user agent would send it to the redirect target. To avoid that, a server can first redirect to an internal interstitial page that has no identifying or sensitive information in its URI, before it processes the request. Once the user agent is ultimately redirected from that page, no part of the original interaction URI will be found in the Referer header 🌸
11.20. Interception of Responses from the AS 👑
Responses from the AS carry information that's vital to both the security and privacy operations of GNAP 🔒 That includes nonces used in cryptographic calculations, Subject Identifiers, assertions, public keys, and info about what the client software asked for and what it was granted.
Here's where it gets dicey 😬 If bearer tokens are used, or keys are issued alongside a bound access token, the response from the AS has everything needed to use the access token inside it. Any party that can view that response, like an intermediary proxy, could exfiltrate and use the token. If the access token is instead bound to the client instance's presented key, intermediaries no longer have enough info to use the token. They can still gain info about the end user and what the client software is doing though, so it's not a total win 🙈
11.21. Key Distribution 🔑
GNAP doesn't define ways for client instance keys to be provided to client instances, especially given how those keys get made known to the AS 🤷♀️ These keys could be generated dynamically on the client software, or pre-registered at the AS in a static developer portal. They could also be distributed as part of the deployment process for instances of the client software. Like, an application installation framework could generate a key pair for each copy of the client software, install it into that software at installation time, and register that instance with the AS. Iconic setup 🧚
Also, the AS can generate keys to be used with access tokens that are separate from the keys the client instance uses to request tokens 💅 In this method, the AS would generate the asymmetric key pair or symmetric key, then return the public key or key reference to the client instance alongside the access token itself. How the AS hands generated key values back to the client instance is out of scope, since GNAP doesn't allow private or shared key info to be transmitted within the protocol itself.
Now the attack surface 👀 If the token is bound to a key other than the client instance's presented key, an attacker's AS could request an access token and then swap in its own key material in the response to the client instance. The attacker's AS would need to be able to use the same key as the client instance, and this setup would let an attacker's AS make use of a compromised key within a system. The fix? Only bind access tokens to the client instance's presented keys, and have client instances keep a strong association between which keys they expect to use and which AS they expect to use them on 🦋 This attack can also only spread to client instances that talk to more than one AS at runtime, and the client software can limit that 💖
11.22. Key Rotation Policy 🔄
When keys get rotated, there could be a delay before that rotation reaches all the components in the AS's ecosystem ⏳ The AS can set its own policy for timing out the previously bound key. It can make the old key immediately obsolete, or allow a limited grace period where both the previously bound key and the current key can be used for signing requests. A grace period is super useful when there are multiple running copies of the client that are coordinated with each other. For example, the client software could be deployed as a cloud service with multiple orchestrated nodes. Each copy uses the same key, so all the nodes represent the same client instance to the AS. Updating the keys on every copy in the exact same instant can be really hard, or even impossible 🫠
The catch: you have to balance accommodating those known delays with the risk of letting an old key keep working 🎯 Narrowly restricting the chances for exploit at the AS, in terms of time, place, and method, makes exploitation way harder, especially if the exception only happens once ✨ For example, the AS can reject requests from the previously bound key (or any previous one before it) to force rotation to a new key, or at least make sure the rotation happens idempotently to the same new key.
See also the related considerations for token values in Section 11.33 📎
11.23. Interaction Finish Modes and Polling 🎀
During the interaction process, the client instance usually hands control of the user experience over to another component, whether that's the system browser, another application, or some action the RO gets told to take on another device 📱 By using an interaction finish method, the client instance can be securely notified by the AS when the interaction is done and the next phase of the protocol should happen. This includes info the client instance can use to validate the finish call from the AS, which helps prevent some injection, session hijacking, and phishing attacks 🛡️
Some types of client deployment can't receive an interaction finish message at all 🥺 Without an interaction finish method to notify it, the client instance will need to poll the grant continuation API while it waits for the RO to approve or deny the request. An attacker could take advantage of this by capturing the interaction start parameters and phishing a legit user into authorizing the attacker's waiting client instance. That waiting instance would have no way to connect the completed interaction from the targeted user with the start of the attacker's request. Yikes 💀
BUT it's important to note this pattern is practically indistinguishable from some legit use cases 🙃 For example, a smart device emits a code for the RO to enter on a separate device. The smart device has to poll because the expected behavior is that the interaction happens on the separate device, with no way to send info back to the original device's context 📟
So developers need to weigh the risks of skipping an interaction finish method against what the client software and its environment can actually support 💜 Because of the increased security, an interaction finish method should be employed whenever possible 🫶
11.24. Session Management for Interaction Finish Methods 🎀
okay so when you use an interaction finish method like redirect or push, the client instance gets an unsolicited inbound request from an unknown party over HTTPS 😳. The client instance has to be able to successfully link that incoming request to the specific pending grant request it's managing. If it isn't careful and precise about this, an attacker could connect their own session at the client instance with a stolen interaction response 💀. How you prevent that depends on the type of client software and the interaction methods in use. Here are some common patterns.
If the end user interacts with the client instance through a web browser and the redirect interaction finish method is used, the client instance can make sure the incoming HTTP request from the finish method shows up in the same browser session that the grant request was started in ✨. This is especially useful when the redirect interaction start mode is used too, because in lots of cases the end user follows the redirection with the same browser they're already using to interact with the client instance. The client instance can then store the relevant pending grant information in the session, either directly in browser storage (like in a single-page application) or in an associated session store on a backend server 🧚. Either way, when the incoming request reaches the client instance, the session info can confirm that the same party who started the request is still there when it finishes.
Making sure the same party who started a request is present when it finishes can prevent phishing attacks 🚩. Picture this: an attacker starts a request at an honest client instance and tricks an honest RO into authorizing it. For example, say an honest end user (who is also the RO) wants to start a request through a client instance the attacker controls. The attacker can start a request at an honest client instance, then redirect the honest end user to the interaction URI from the attacker's session with the honest client instance. If the honest end user doesn't realize that they're not authorizing the attacker-controlled client instance they started with, but are instead authorizing the honest client instance while interacting with the AS, the attacker's session with the honest client instance gets authorized 😤. That would give the attacker access to the honest end user's resources that the honest client instance is authorized to reach. But if, after the interaction, the AS redirects the honest end user back to the client instance whose grant request they just authorized, the honest end user gets redirected to the honest client instance. The honest client instance can then notice that the end user isn't the party that started the request, since the request there was started by the attacker, and that detection stops the attack 🙌. This connects to the discussion in Section 11.15, because again the attack can be prevented by having the AS tell the user as much as possible about the client instance that's about to be authorized.
If the end user doesn't interact with the client instance through a web browser, or if the interaction start method doesn't use the same browser or device the end user is interacting through (like launching a second device through a scannable code or showing a user code), the client instance won't be able to strongly tie an incoming HTTP request to an established session with the end user 🥺. The same goes when the push interaction finish method is used, because the HTTP request comes straight from the interaction component of the AS. In these situations, the client instance can at least make sure the incoming HTTP request can be uniquely matched to an ongoing grant request by making the interaction finish callback URI unique for the grant when it makes the interaction request (Section 2.5.2) 🦋. Mobile applications and other client instances that usually serve only one end user at a time can use this unique incoming URL to tell a legitimate incoming request apart from an attacker's stolen one 💖.
11.25. Calculating Interaction Hash 🔐
Okay, GNAP's signing mechanisms and token-protected grant API give the protocol some seriously strong security 💪. But the interaction reference mechanism is still open to monitoring, capture, and injection by an attacker 😬. To fight this, GNAP requires the calculation and verification of an interaction hash. A client instance might be tempted to skip this step, but doing that leaves it open to injection and manipulation by an attacker, which could lead to even more issues 🙈.
Calculating the interaction hash value gives you defense in depth, so a client instance can protect itself from spurious injection of interaction references when it uses an interaction finish method 🛡️. The AS is protected during this attack because the continuation access token is bound to the expected interaction reference. But without the hash calculation, the attacker could make the client send an HTTP request on command, and that request could be manipulated too. For example, a malicious value could be slipped into the interaction reference to attack the AS 😱. With both of these in place, an attacker trying to substitute the interaction reference gets stopped in several places 💅.
.----. .------. +--------+ +--------+
| User | |Attacker| | Client | | AS |
| | | | |Instance| | |
| | | | | | | |
| | | +=(1)=>| | | |
| | | | | +-(2)->| |
| | | | | |<-(3)-+ |
| | | |<=(4)=+ | | |
| | | | | | | |
| | | +==(5)================>| |
| | | | | | | |
| | | |<================(6)==+ |
| | | | | | | |
| +==(A)================>| | | |
| | | | | +-(B)->| |
| | | | | |<-(C)-+ |
| |<=================(D)=+ | | |
| | | | | | | |
| +==(E)================================>| |
| | | | | | | |
| |<=(7)=+ | | | | |
| | | | | | | |
| +==(F)================>| | | |
| | | | | +-(G)->| |
| | | | | | | |
`----` `------` +--------+ +--------+
Figure 11: Interaction Hash Attack 🦋
Okay, here's how the setup goes 🎬. Prerequisites: the client instance can let multiple end users access the same AS. The attacker is trying to hook their rights up to the target user's session 😈.
- (1) The attacker starts a session at the client instance.
- (2) The client instance creates a grant request with nonce CN1.
- (3) The AS responds to the grant request with a need to interact, nonce SN1, and a continuation token, CT1.
- (4) The client instructs the attacker to interact at the AS.
- (5) The attacker interacts at the AS.
- (6) The AS completes the interact finish with interact reference IR1 and interact hash IH1 calculated from
(CN1 + SN1 + IR1 + AS). The attacker stops IR1 from getting back to the client instance 🚫. - (A) The target user starts a session at the client instance.
- (B) The client instance creates a grant request with nonce CN2.
- (C) The AS responds to the grant request with a need to interact, nonce SN2, and a continuation token, CT2.
- (D) The client instance instructs the user to interact at the AS.
- (E) The target user interacts at the AS.
- (7) Before the target user can finish their interaction, the attacker delivers their own interact reference IR1 into the user's session. The attacker can't calculate the right hash because they don't have access to CN2 and SN2 🙅.
- (F) The target user triggers the interaction finish in their own session with the attacker's IR1.
- (G) If the client instance checks the interaction hash, the attack stops right here, because the hash calculation of
(CN2 + SN2 + IR1 + AS)will fail ✅. If the client instance doesn't check the interaction hash, it gets tricked into submitting the interaction reference to the AS. The AS would then reject the interaction request, since it's presented against CT2 and not CT1 as expected. But an attacker who has potentially injected CT1 as the value of CT2 could keep the attack going 😭.
Even with extra checks in place, client instances that use interaction finish mechanisms are responsible for checking the interaction hash to keep the overall system secure 🔒.
11.26. Storage of Information during Interaction and Continuation 💜
When you start an interactive grant request, a client application has a bunch of protocol elements to manage, including nonces, references, keys, access tokens, and other stuff 🗂️. During the interaction, the client instance usually hands control of the user experience over to another component, whether that's the system browser, another application, or an action the RO is told to take on another device. To make its continuation call, the client instance has to recall all of those protocol elements later on. Usually that means storing them somewhere they can be retrieved ✨.
If the security protocol elements are stored on the end user's device, like in browser storage or in local application data stores, then capturing and exfiltrating that info could let an attacker continue a pending transaction instead of the client instance 🕵️. Client software can use secure storage mechanisms, including hardware-based key and data storage, to prevent that kind of exfiltration 🔐.
Here's a key detail 👀: in GNAP, the client instance has to choose its interaction finish URI before making the first call to the AS. So the interaction finish URI will often include a unique identifier for the ongoing request, which lets the client instance reach the correct portion of its storage 🗝️. Since this URI gets passed to other parties and is often used through a browser, it should not contain any security-sensitive information that would be valuable to an attacker, like a token identifier, a nonce, or user information 🙅♀️. Instead, a cryptographically random value is suggested, and that value should be used to index into a secure session or storage mechanism 🌈.
11.27. Denial of Service (DoS) through Grant Continuation ✨
okay so bestie, when a client instance starts an interactive process, it's eventually gonna need to continue the grant request in a later message to the AS 💅. A naive client could literally spam continuation requests at the AS while it waits for approval, especially if no interaction finish method is used 💀. Those constant requests could totally overwhelm the AS's ability to respond to both these and everything else 😭.
To help well-behaved client software, the continuation response has a wait parameter 🎀 that tells the client instance how long it should wait before making its next request ⏳. This value can be used to back off software that's checking too fast by returning increasing wait times for a single client instance 📈.
If client software ignores the wait value and makes its continuation calls too quickly, or if it assumes that no wait value means it should poll immediately 🙈, the AS can choose to return errors to the offending client instance, including possibly canceling the ongoing grant request 🚫. With well-meaning client software, these errors can be a sign that the software's programmed behavior needs to change 🫶.
11.28. Exhaustion of Random Value Space 🎰
so GNAP runs on unguessable randomized values 🔐, like nonces, tokens, user codes, and randomized URIs. These are supposed to be unique, right? So a sufficiently powerful attacker could send a huge pile of requests just to trigger generation of random values and try to exhaust the random number generation space 😱. This attack is especially relevant to the AS, but client software could get targeted too, with an attacker triggering new grant requests against an AS 👀.
To protect against this, software can make sure its random values come from a significantly large pool, so exhausting that pool is prohibitive for an attacker 💪. Random values can also be time-boxed so their validity windows are reasonably short ⏱️. Lots of random values in GNAP only get used in small corners of the protocol, so it's reasonable for a particular random value to be valid for only a short time 🫧. For example, the nonces used for interaction finish hash calculation only need to be valid while the client instance is waiting for the finish callback, and they can be functionally expired once the interaction has completed 🏁. Access tokens and the interaction reference can get lifetimes tied to their functional utility too 🎟️. And finally, each category of artifact (nonce, token, reference, identifier, etc.) can be generated from its own separate random pool instead of one single global value space 🧩.
11.29. Front-Channel URIs 🌐
okay this one's important, no cap. Some interaction methods in GNAP use URIs that get accessed through the end user's browser, which is known collectively as front-channel communication 🖥️. The big ones are the redirect interaction start method and the redirect interaction finish mode 🔀. Since these URIs are meant to be handed to the end user, the end user and their browser will be subjected to anything hosted at that URI, including viruses, malware, and phishing scams 🦠😬. This risk is inherent to all redirection-based protocols, GNAP included, when used this way 💀.
When talking to a new or unknown AS, a client instance might want to check the URI from the interaction start against a blocklist and warn the end user before redirecting them 🚨. Lots of client instances show an interstitial message before redirecting, to prepare the user for control of the experience being handed over to the AS's domain 🪧. That's a great moment to warn the user about potential threats, like a rogue AS impersonating a well-known service provider 🕵️♀️. Client software can also prevent this by managing an allowlist of known and trusted ASes ✅.
Alternatively, an attacker could start a GNAP request with a known and trusted AS, but include their own attack site URI as the callback for the redirect finish method 😈. Then they send the interaction start URI to the victim and get them to click it 🎣. Since the URI lives at the known AS, the victim is inclined to click it 🙃. The victim gets prompted to approve the attacker's application, and in most circumstances, they'll be redirected to the attacker's site whether or not they approved the request 💔. The AS could partly mitigate this by using a blocklist and allowlist of interaction finish URIs during the client instance's initial request, but that's especially hard if the URI has any dynamic portion chosen by the client software 🧐. The AS can couple these checks with policies tied to the client instance that was authenticated in the request 🔗. And if the AS has any doubt about the interaction finish URI, it can show an interstitial warning to the end user before processing the redirect 🚧.
Ultimately, every protocol that uses redirect-based communication through the user's browser is susceptible to an attacker trying to co-opt one or more of those URIs to harm the user. It's the responsibility of the AS and the client software to provide appropriate warnings, education, and mitigation to protect end users 🫶🌸
11.30. Processing Assertions 📜
identity assertions can carry subject information in GNAP 🪪. They go from the AS to the client instance in a response (Section 3.4), and from the client instance to the AS in a request (Section 2.2) 🔁. In both directions, when an assertion is passed, the receiver has to parse and process it 🧾. Assertions are complex artifacts with their own syntax and security, so special care is needed to keep the assertion values from becoming an attack vector ⚠️.
All assertion processing has to account for the security aspects of the assertion format in use 🔒. Specifically, the processor needs to parse the assertion from a JSON string object and apply the appropriate cryptographic processes to make sure the assertion's integrity holds up 🧠.
For example, when SAML 2.0 assertions are used, the receiver has to parse an XML document 📄. There are many well-known security vulnerabilities in XML parsers, and the XML standard itself can be attacked through processing instructions and entity expansions that cause problems for the processor 😵. So any system that can process SAML 2.0 assertions also needs a secure and correct XML parser 💅. On top of that, the SAML 2.0 specification uses XML Signatures, which come with their own implementation problems that have to be accounted for 🧩. Similar requirements exist for OpenID Connect ID Token, which is based on the JWT format and the related JOSE cryptography suite 🗝️.
11.31. Stolen Token Replay 🕵️♀️
okay the plot 🍿. If a client instance can request tokens at multiple ASes, and it uses the same keys for its requests across those different ASes, then an attacker can replay a stolen token that an honest AS issued, coming from a compromised AS 😱. That binds the stolen token to the client instance's key in a different context 🔗. The attacker can then manipulate the client instance into using the stolen token at an RS, especially an RS that expects a token from the honest AS 🎯. Since the honest AS issued the token and the client instance presents it with its expected bound key, the attack succeeds 💀.
This attack has a few preconditions 📋. The attacker doesn't need access to the client instance's key and can't use the stolen token directly at the RS, but they are able to get the access token value somehow 👀. The client instance also needs to be configured to talk to multiple ASes, including the attacker's controlled AS 🌀. And finally, the attacker has to be able to manipulate the client instance into calling the RS while using a token issued by the stolen AS 🪤. The RS doesn't need to be compromised or made to trust the attacker's AS 🙅♀️.
To protect against this attack, the client instance can use a different key for each AS it talks to 🔑. The replayed token is bound to the key used at the honest AS, so the uncompromised RS will reject the call, since the client instance would be using the key from the attacker's AS with the same token 🚫. When the MTLS key proofing method is used, a client instance can use self-signed certificates to have a different key for each AS it talks to, as discussed in Section 11.4 📜✨. Additionally, the client instance can keep a strong association between the RS and a specific AS it trusts to issue tokens for that RS 🤝. This strong binding also helps against some forms of AS mix-up attacks (Section 11.12) 🧠. Managing this binding is outside the scope of this specification, but it can be managed either as a configuration element for the client instance or dynamically by discovering the AS from the RS (Section 9.1) 🔍.
The details of this attack, with additional discussion and considerations, are available in Section 3.2 of [HELMSCHMIDT2022] 📚🦋.
11.32. Self-Contained Stateless Access Tokens 🧚
the contents and format of the access token are up to the AS, and they're opaque to the client instance within GNAP 🫥. As discussed in [GNAP-RS], the AS and RS can use stateless access tokens with an internal structure and format 🧱. These let an RS validate the token without making any external calls at runtime, which has benefits in some deployments, though discussing those is outside the scope of this specification 🌈.
BUT, and this is a big but 🚨, using self-contained access tokens affects what the AS can do with certain functionality defined in this specification. Since the access token is self-contained, it's difficult or impossible for an AS to signal to all RSs in an ecosystem when a specific access token has been revoked 🫠. So an AS in that kind of ecosystem should probably not offer token revocation functionality to client instances, since the client instance's calls to that endpoint are effectively meaningless 🙃. Still, a client instance that calls the token revocation function will also throw out its copy of the token, so a placebo endpoint like that might not be completely meaningless 🤷♀️. Token rotation is similarly tricky, because the AS has to revoke the old access token after a rotation call has been made 🔄. If access tokens are completely self-contained and non-revocable, there will be a period of time when both the old and new access tokens are valid and usable, which is an increased security risk for the environment 😬.
These problems can be mitigated by keeping the validity time windows of self-contained access tokens reasonably short, which limits how long a revoked token could be used after a revocation event ⏳✨. The AS could also proactively signal to RSs under its control identifiers for revoked tokens that haven't expired yet 📣. This kind of information push would be expected to be relatively small and infrequent, and its implementation is outside the scope of this specification 💜.
11.33. Network Problems and Token and Grant Management ✨
okay so picture this, bestie 😭 a client instance tries to rotate its access token, but the network drops before the response with the new token gets back to it. The AS has already rotated the old token and invalidated it, but the client instance only has the dead old token and none of the shiny new value 💀. If it retries the rotation, it fails, because it's no longer presenting a valid and current access token. A similar mess can happen during grant continuation, where the same client instance tries to continue or update a grant request and never gets the results of that update 🙈
To fix this, both grant management (Section 5) and token management (Section 6) can be designed to be idempotent 🫶. That means subsequent calls to the same function with the same credentials are meant to produce the same results. Like, multiple calls to rotate the same access token need to result in the same rotated token value, within a reasonable time window. Main idempotency energy 💅
In practice, an AS can hold onto an old token value for these limited purposes ✨. For example, to support rotating access tokens over unreliable networks, the AS receives the initial request to rotate an access token, creates a new token value, and returns it. The AS also marks the old token value as having been used to create the newly rotated token value. If the AS sees the old token value within a small enough time window, such as a few seconds since the first rotation attempt, the AS can return the same rotated access token value. Furthermore, once the system has seen the newly rotated token in use, the original token can be discarded, because the client instance has proved that it did receive the token. The result is a system that is eventually self-consistent, without putting an undue complexity burden on the client instance to manage problematic networks. Consistency without the client doing all the heavy lifting 🌸
11.34. Server-Side Request Forgery (SSRF) 🕸️
okay this one is seriously spooky, so buckle up 😱 There are several places within GNAP where a URI can be given to a party, causing that party to fetch the URI during normal operation of the protocol. If an attacker can control the value of one of these URIs, they could make the target system execute a request on a URI that is within reach of the target but normally unavailable to the attacker. Examples include an attacker sending http://localhost/admin to get the server to access an internal function on itself, or https://192.168.0.14/ to call a service behind a firewall. Even if the attacker never sees the results of the call, the side effects of requests coming from a trusted host can wreck the security and sanctity of those otherwise unexposed endpoints. It gets extra problematic if the URI is used to call non-HTTP endpoints, such as remote code execution services local to the AS 💀
The most vulnerable place in this specification is the push-based post-interaction finish method (Section 4.2.2). The client instance is less trusted than the AS, and it can use this method to make the AS call an arbitrary URI 🚨. While the protocol doesn't require it, the AS can fetch other URIs provided by the client instance, such as the logo image or home page, for verification or privacy-preserving purposes before showing them to the RO as part of a consent screen. Even if the AS doesn't fetch these URIs, their use in GNAP's normal operation could still enable an attack against the end user's browser, since the browser fetches these same attack URIs. Extensions to GNAP that allow or require URI fetch could be just as susceptible. One example is a system where the AS fetches a client instance's keys from a presented URI instead of the client instance presenting the key by value. Such extensions are outside the scope of this specification, but any system deploying one would need to be aware of this issue ⚠️
To help mitigate this problem, similar approaches that protect parties against malicious redirects (Section 11.29) can be used 🧚. For example, all URIs that can result in a direct request being made by a party in the protocol can be filtered through an allowlist or blocklist. An AS that supports the push-based interaction finish method can compare the callback URI in the interaction request to a known URI for a pre-registered client instance, or it can make sure the URI isn't on a blocklist of sensitive URLs, such as internal network addresses. However, note that because these calls happen outside the view of human interaction, it's usually not feasible to notify and warn someone before the request needs to be executed, the way you can with redirection URLs. As such, SSRF is somewhat harder to manage at runtime, and systems should generally refuse to fetch a URI if unsure. When in doubt, don't fetch it, bestie 🙅♀️
11.35. Multiple Key Formats 🔑
All keys presented by value are only allowed to be in a single format 🎀. I know, it sounds like it would be helpful to let keys come in multiple formats in case the receiver doesn't understand one of them. But there are security issues with that. If multiple key formats were allowed, receivers would need to make sure it's the same key represented in each field, and not just use one of the formats without checking for equivalence. If equivalence isn't carefully checked, an attacker could insert their own key into one of the formats without needing control over the other formats. That could lead to a messed-up situation where one key is used by part of the system (such as identifying the client instance) and a different key, in a different format, in the same message is used for other things (such as calculating signature validity). And the receiver can't fully verify that every format contains the same key information, because it's assumed the receiver can't understand all of the formats 😬
To combat this, all keys presented by value have to be in exactly one supported format that the receiver knows, as discussed in Section 7.1 🌈. In most cases, a client instance is configured with its keys in a single format, and it just presents that format as is to the AS in its request. A client instance that is capable of multiple formats can use AS discovery (Section 9) to figure out which formats are supported, if it wants to. An AS should be generous in supporting many different key formats, so different types of client software and client instance deployments can connect 💖. An AS implementation should try to support multiple formats so a variety of client software can connect.
11.36. Asynchronous Interactions 📬
GNAP lets the AS contact the RO asynchronously, outside the regular flow of the protocol. This opens the door to some advanced use cases, such as cross-user authentication or information release. But those advanced use cases come with distinct issues that implementors need to be fully aware of before using these features 👀
First, in many applications, returning subject information to the client instance could signal that the end user is the party represented by that information. That basically lets the end user authenticate to the client application 🫣. The details of a fully functional authentication protocol are outside the scope of GNAP, but it's a common exercise for a client instance to request information about the end user. Several interaction methods (Section 4.1) defined in GNAP make this possible by letting the end user begin interaction directly with the AS. However, when the subject of the information is intentionally not the end user, the client application needs some way to tell the difference between requests to authenticate the end user and requests for information about a different user. Mixing those states up could let an attacker have their account associated with a privileged user 💀. Client instances can prevent this by having distinct code paths for primary end-user authentication and for requesting subject information about secondary users, like in a call center 📞. In those use cases, the client software used by the RO (the caller) and the client software used by the end user (the agent) are generally distinct, which lets the AS tell apart the agent's corporate device making the request from the caller's personal device approving it.
Second, ROs who interact asynchronously usually don't have the same context as an end user in an application that's trying to do the task needing authorization. So the asynchronous authorization requests coming to the RO from the AS might have very little to do with what the RO is doing at that moment 😵💫. This can lead to authorization fatigue, where any incoming authorization request gets quickly approved and dispatched without the RO properly verifying it. An attacker can exploit that fatigue and get the RO to authorize the attacker's system for access 🚩. To mitigate this, AS systems deploying asynchronous authorization should only prompt the RO when the RO is expecting such a request. Significant user experience engineering needs to go into making sure the RO can clearly make the appropriate security decision 🎯. Furthermore, audit capability and the ability to undo access decisions that may be ongoing are especially important in the asynchronous case 🔍.
11.37. Compromised RS 🏴☠️
An attacker may aim to get access to confidential or sensitive resources. The measures for hardening and monitoring RS systems, beyond protection with access tokens, are out of the scope of this document. But using GNAP to protect a system does not excuse the RS from following best practices 🧼. GNAP generally assumes a breach can occur, so it advises preferring key-bound tokens whenever possible 🔐. That at least limits the impact of access token leakage by a compromised or malicious RS. Assume breach, prefer key-bound tokens, slay forever 👑
11.38. AS-Provided Token Keys ✨
okay so the most common move is binding the access token to the client instance's presented key 🔑. but the AS can also hand over a binding key along with the access token, which you'll see in the key field of the token response in Section 3.2.1. this lets the AS generate and manage the keys tied to tokens on its own, separate from the keys client instances already know about 🧚
if the key material comes back by value from the AS, the client instance just uses that key value when it presents the token. and this is where it gets sus 😭 an attacker can exploit this to hand a compromised token to an unsuspecting client, as long as the client trusts the attacker's AS to issue tokens for the target RS. the attacker first gets a token bound to a key they control, and that token is probably tied to an authorization or account the attacker runs. then they reissue that same token to the client instance, this time acting as an AS 💀. they return their own key to the client, and now the client is using the attacker's token. this also works when the key comes back by reference, if the attacker can supply a reference that means something to the client instance and points to a key under the attacker's control. it's basically the same substitution energy as the main bearer token problems we covered in Section 11.9 🙈
returning a key with an access token should only happen when both the client and the AS can be verified as honest, and when skipping the client instance's own keys is worth the extra risk. ngl that's a big if 🫶
12. Privacy Considerations 🌸
this section is modeled after the list of privacy threats in "Privacy Considerations for Internet Protocols" [RFC6973], and it either explains how those threats get mitigated or advises how they relate to GNAP 💜
12.1. Surveillance 👀
surveillance is the observation or monitoring of someone's communications or activities. it can be done by observers or eavesdroppers at any point along the communications path 🦋
GNAP assumes the TLS protection used throughout the spec is intact. without TLS, there are tons of points in GNAP where surveillance could happen. even with TLS used properly, surveillance could still come from several parties outside the TLS-protected channels, as the subsections below explain 🔥
12.1.1. Surveillance by the Client
the whole point of GNAP is to let clients access information on behalf of a user. so the client is expected to know the user's identity and the data being fetched for that user. but sometimes what the client does can go way beyond what the user is aware of 😳 for example, a client might be built from multiple separate pieces of software, like a logging service or a mobile app that reports usage data to an external backend. each of those pieces could learn stuff about the user without the user ever knowing 🙈
when the client software uses a hosted asset for one of its components, like its logo image, fetching that asset can reveal user actions to the host. if the AS presents the logo URI to the RO in a browser page, the browser fetches the logo from the authorization screen. that fetch tells the logo's host that someone is accessing an instance of the client and requesting access for it. this is especially sus when the asset's host isn't the client software itself, like when a content delivery network is involved 😭
12.1.2. Surveillance by the Authorization Server
the AS's whole role is managing the authorization of client instances to protect the user's data. so by definition, it is aware of each authorization of a client instance by a user 👑. when the AS shares user information with the client instance, it needs to make sure it has the user's permission to do so ✨
also, as part of the grant process, the AS may be aware of which RSs the client intends to use an access token at. but it's possible to design a GNAP system where this knowledge isn't made available to the AS, such as by avoiding the use of the locations object in the authorization request 🧚
if the AS's access token implementation requires an RS callback to the AS to validate them, then the AS will be aware of which RSs are actively in use and by which users and clients. to avoid this, the AS would need to structure access tokens so they can be validated by the RS without notifying the AS that the token is being validated 🦋
12.2. Stored Data 🗂️
several parties in the GNAP process are expected to persist data at least temporarily, if not semi-permanently, for the system to function normally. if that data gets compromised, it could lead to exposure of sensitive information 😬. this section documents the potentially sensitive information each party is expected to store for normal operation. and yes, any party can store protocol mechanics info (like audit logs) for longer than is technically necessary, so that's always a thing 🙃
the AS is expected to store Subject Identifiers for users indefinitely, so it can include them in responses to clients. the AS is also expected to store client key identifiers associated with display information about the client, like its name and logo 💅
the client is expected to store its client instance key indefinitely, so it can authenticate to the AS for the normal functioning of GNAP flows. the client will also be temporarily storing artifacts issued by the AS during a flow, and those artifacts ought to be discarded by the client when the transaction is complete 🧹
the RS is not required to store any state for its normal operation, as far as its part in implementing GNAP goes. depending on how access tokens are implemented, the RS may need to cache public keys from the AS in order to validate access tokens 🌈
12.3. Intrusion 📨
intrusion is about the ability of various parties to send unsolicited messages or cause denial of service for unrelated parties 🚨
if the RO is different from the end user, there's an opportunity for the end user to cause unsolicited messages to be sent to the RO. this happens if the system prompts the RO for consent whenever an end user tries to access their data 😤
the format and contents of Subject Identifiers are intentionally not defined by GNAP. if the AS uses values for Subject Identifiers that are also identifiers for communication channels (for example, an email address or phone number), this opens up the possibility for a client to learn that information when it wasn't otherwise authorized to access this kind of data about the user 📱
12.4. Correlation 🧩
the threat of correlation is combining various pieces of information about an individual in a way that defies their expectations of what others know about them 🕵️♀️
12.4.1. Correlation by Clients
the biggest correlation risk in GNAP is when an AS returns stable, consistent user identifiers to multiple different applications. in that case, applications made by different parties would be able to correlate those user identifiers out of band to figure out which users they have in common 😱
the most common real-world example is tracking for advertising. a client shares its list of user IDs with an ad platform, which can then retarget ads to applications created by other parties. on the flip side, a positive example of correlation is a corporate acquisition where two previously unrelated clients now do need to identify the same user across both clients, like when software systems are intentionally connected by the end user 🤝
another source of correlation comes from RS-first discovery (Section 9.1). a client instance that knows nothing other than an RS's URL could make an unauthenticated call to the RS and learn which AS protects the resources there. and if the client instance knows something about the AS, like it being a single-user AS or belonging to a specific organization, the client could, through association, learn things about the resource without ever gaining access to the resource itself 🫣
12.4.2. Correlation by Resource Servers
unrelated RSs also have a chance to correlate users if the AS includes stable user identifiers in access tokens or in access token introspection responses 👀
sometimes an RS doesn't actually need to identify users. like an RS providing access to a company cafeteria menu only needs to validate whether the user is a current employee. so ASes should be thoughtful about when user identifiers are actually necessary to communicate to RSs for the system to work 🍓
BUT (sorry, lol) not including a user identifier in an access token can be a risk too, if there's a concern that two users may voluntarily share access tokens between them to get into protected resources. for example, if a website wants to limit access to people over 18, and it doesn't need to know any user identifiers, an access token may be issued by an AS that only contains the claim "over 18". if the user knows this token doesn't reference them individually, they might be willing to share it with someone under 18 so that person can get into the website. (fun fact: binding an access token to a non-extractable client instance key also stops the token from being voluntarily shared 🔐)
12.4.3. Correlation by Authorization Servers
clients are expected to be identified by their client instance key. if a particular client instance key is used at more than one AS, that could open up the possibility for multiple unrelated ASes to correlate client instances 🕸️. this is especially a problem in the common case where a client instance is used by a single individual, since it would let the ASes correlate that individual between them. if this is a concern for a client, the client should use distinct keys with each AS 🔑
12.5. Disclosure in Shared References 🎀
throughout many parts of GNAP, the parties pass shared references to each other, sometimes in place of the values themselves (for example, the interact_ref value used throughout the flow). these references are intended to be random strings, and they should not contain any private or sensitive data that could potentially leak information between parties 🤫
13. References 📚
13.1. Normative References ✨
okay bestie, these are the docs the spec leans on so hard it can't work without them 🫶
[BCP195]'s entry is a whole Best Current Practice, so here it is with its contents:
[BCP195] Best Current Practice 195,
<https://www.rfc-editor.org/info/bcp195>.
At the time of writing, BCP 195 is made up of these two docs 🌈
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>.
[HASH-ALG] IANA, "Named Information Hash Algorithm Registry",
<https://www.iana.org/assignments/named-information/>.
[HTTP] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke,
Ed., "HTTP Semantics", STD 97, RFC 9110,
DOI 10.17487/RFC9110, June 2022,
<https://www.rfc-editor.org/info/rfc9110>.
[OIDC] Sakimura, N., Bradley, J., Jones, M., de Medeiros, B., and
C. Mortimore, "OpenID Connect Core 1.0 incorporating
errata set 2", December 2023,
<https://openid.net/specs/openid-connect-core-1_0.html>.
[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>.
[RFC2397] Masinter, L., "The "data" URL scheme", RFC 2397,
DOI 10.17487/RFC2397, August 1998,
<https://www.rfc-editor.org/info/rfc2397>.
[RFC3339] Klyne, G. and C. Newman, "Date and Time on the Internet:
Timestamps", RFC 3339, DOI 10.17487/RFC3339, July 2002,
<https://www.rfc-editor.org/info/rfc3339>.
[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>.
[RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data
Encodings", RFC 4648, DOI 10.17487/RFC4648, October 2006,
<https://www.rfc-editor.org/info/rfc4648>.
[RFC5646] Phillips, A., Ed. and M. Davis, Ed., "Tags for Identifying
Languages", BCP 47, RFC 5646, DOI 10.17487/RFC5646,
September 2009, <https://www.rfc-editor.org/info/rfc5646>.
[RFC6749] Hardt, D., Ed., "The OAuth 2.0 Authorization Framework",
RFC 6749, DOI 10.17487/RFC6749, October 2012,
<https://www.rfc-editor.org/info/rfc6749>.
[RFC6750] Jones, M. and D. Hardt, "The OAuth 2.0 Authorization
Framework: Bearer Token Usage", RFC 6750,
DOI 10.17487/RFC6750, October 2012,
<https://www.rfc-editor.org/info/rfc6750>.
[RFC7468] Josefsson, S. and S. Leonard, "Textual Encodings of PKIX,
PKCS, and CMS Structures", RFC 7468, DOI 10.17487/RFC7468,
April 2015, <https://www.rfc-editor.org/info/rfc7468>.
[RFC7515] Jones, M., Bradley, J., and N. Sakimura, "JSON Web
Signature (JWS)", RFC 7515, DOI 10.17487/RFC7515, May
2015, <https://www.rfc-editor.org/info/rfc7515>.
[RFC7517] Jones, M., "JSON Web Key (JWK)", RFC 7517,
DOI 10.17487/RFC7517, May 2015,
<https://www.rfc-editor.org/info/rfc7517>.
[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>.
[RFC8705] Campbell, B., Bradley, J., Sakimura, N., and T.
Lodderstedt, "OAuth 2.0 Mutual-TLS Client Authentication
and Certificate-Bound Access Tokens", RFC 8705,
DOI 10.17487/RFC8705, February 2020,
<https://www.rfc-editor.org/info/rfc8705>.
[RFC9111] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke,
Ed., "HTTP Caching", STD 98, RFC 9111,
DOI 10.17487/RFC9111, June 2022,
<https://www.rfc-editor.org/info/rfc9111>.
[RFC9421] Backman, A., Ed., Richer, J., Ed., and M. Sporny, "HTTP
Message Signatures", RFC 9421, DOI 10.17487/RFC9421,
February 2024, <https://www.rfc-editor.org/info/rfc9421>.
[RFC9493] Backman, A., Ed., Scurtescu, M., and P. Jain, "Subject
Identifiers for Security Event Tokens", RFC 9493,
DOI 10.17487/RFC9493, December 2023,
<https://www.rfc-editor.org/info/rfc9493>.
[RFC9530] Polli, R. and L. Pardue, "Digest Fields", RFC 9530,
DOI 10.17487/RFC9530, February 2024,
<https://www.rfc-editor.org/info/rfc9530>.
[SAML2] Cantor, S., Ed., Kemp, J., Ed., Philpott, R., Ed., and E.
Maler, Ed., "Assertions and Protocol for the OASIS
Security Assertion Markup Language (SAML) V2.0", OASIS
Standard, March 2005, <https://docs.oasis-
open.org/security/saml/v2.0/saml-core-2.0-os.pdf>.
13.2. Informative References 🦋
these ones are the extra reading for the curious 🧚 (totally optional, but iconic if you go down the rabbit hole)
[Auth-Schemes]
IANA, "HTTP Authentication Schemes",
<https://www.iana.org/assignments/http-authschemes>.
[AXELAND2021]
Axeland, Å. and O. Oueidat, "Security Analysis of Attack
Surfaces on the Grant Negotiation and Authorization
Protocol", Master's thesis, Department of Computer Science
and Engineering, Chalmers University of Technology and
University of Gothenburg, 2021,
<https://hdl.handle.net/20.500.12380/304105>.
[GNAP-REG] IANA, "Grant Negotiation and Authorization Protocol
(GNAP)", <https://www.iana.org/assignments/gnap>.
[GNAP-RS] Richer, J., Ed. and F. Imbault, "Grant Negotiation and
Authorization Protocol Resource Server Connections", Work
in Progress, Internet-Draft, draft-ietf-gnap-resource-
servers-09, 23 September 2024,
<https://datatracker.ietf.org/doc/html/draft-ietf-gnap-
resource-servers-09>.
[HELMSCHMIDT2022]
Helmschmidt, F., "Security Analysis of the Grant
Negotiation and Authorization Protocol", Master's thesis,
Institute of Information Security, University of Stuggart,
DOI 10.18419/opus-12203, 2022,
<http://dx.doi.org/10.18419/opus-12203>.
[MediaTypes]
IANA, "Media Types",
<https://www.iana.org/assignments/media-types>.
[OAUTH-SEC-TOPICS]
Lodderstedt, T., Bradley, J., Labunets, A., and D. Fett,
"OAuth 2.0 Security Best Current Practice", Work in
Progress, Internet-Draft, draft-ietf-oauth-security-
topics-29, 3 June 2024,
<https://datatracker.ietf.org/doc/html/draft-ietf-oauth-
security-topics-29>.
[promise-theory]
Bergstra, J. and M. Burgess, "Promise Theory: Principles
and Applications", Second Edition, XtAxis Press, 2019,
<http://markburgess.org/promises.html>.
[RFC2046] Freed, N. and N. Borenstein, "Multipurpose Internet Mail
Extensions (MIME) Part Two: Media Types", RFC 2046,
DOI 10.17487/RFC2046, November 1996,
<https://www.rfc-editor.org/info/rfc2046>.
[RFC4107] Bellovin, S. and R. Housley, "Guidelines for Cryptographic
Key Management", BCP 107, RFC 4107, DOI 10.17487/RFC4107,
June 2005, <https://www.rfc-editor.org/info/rfc4107>.
[RFC6202] Loreto, S., Saint-Andre, P., Salsano, S., and G. Wilkins,
"Known Issues and Best Practices for the Use of Long
Polling and Streaming in Bidirectional HTTP", RFC 6202,
DOI 10.17487/RFC6202, April 2011,
<https://www.rfc-editor.org/info/rfc6202>.
[RFC6838] Freed, N., Klensin, J., and T. Hansen, "Media Type
Specifications and Registration Procedures", BCP 13,
RFC 6838, DOI 10.17487/RFC6838, January 2013,
<https://www.rfc-editor.org/info/rfc6838>.
[RFC6973] Cooper, A., Tschofenig, H., Aboba, B., Peterson, J.,
Morris, J., Hansen, M., and R. Smith, "Privacy
Considerations for Internet Protocols", RFC 6973,
DOI 10.17487/RFC6973, July 2013,
<https://www.rfc-editor.org/info/rfc6973>.
[RFC7518] Jones, M., "JSON Web Algorithms (JWA)", RFC 7518,
DOI 10.17487/RFC7518, May 2015,
<https://www.rfc-editor.org/info/rfc7518>.
[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>.
[RFC8264] Saint-Andre, P. and M. Blanchet, "PRECIS Framework:
Preparation, Enforcement, and Comparison of
Internationalized Strings in Application Protocols",
RFC 8264, DOI 10.17487/RFC8264, October 2017,
<https://www.rfc-editor.org/info/rfc8264>.
[RFC8707] Campbell, B., Bradley, J., and H. Tschofenig, "Resource
Indicators for OAuth 2.0", RFC 8707, DOI 10.17487/RFC8707,
February 2020, <https://www.rfc-editor.org/info/rfc8707>.
[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>.
[RFC9396] Lodderstedt, T., Richer, J., and B. Campbell, "OAuth 2.0
Rich Authorization Requests", RFC 9396,
DOI 10.17487/RFC9396, May 2023,
<https://www.rfc-editor.org/info/rfc9396>.
[RFC9440] Campbell, B. and M. Bishop, "Client-Cert HTTP Header
Field", RFC 9440, DOI 10.17487/RFC9440, July 2023,
<https://www.rfc-editor.org/info/rfc9440>.
[RFC9525] Saint-Andre, P. and R. Salz, "Service Identity in TLS",
RFC 9525, DOI 10.17487/RFC9525, November 2023,
<https://www.rfc-editor.org/info/rfc9525>.
[SP80063C] Grassi, P., Richer, J., Squire, S., Fenton, J., Nadeau,
E., Lefkovitz, N., Danker, J., Choong, Y., Greene, K., and
M. Theofanos, "Digital Identity Guidelines: Federation and
Assertions", NIST SP 800-63C, DOI 10.6028/NIST.SP.800-63c,
June 2017,
<https://nvlpubs.nist.gov/nistpubs/SpecialPublications/
NIST.SP.800-63c.pdf>.
[Subj-ID-Formats]
IANA, "Subject Identifier Formats",
<https://www.iana.org/assignments/secevent>.
Appendix A. Comparison with OAuth 2.0 🦋
okay so GNAP's protocol design is different from OAuth 2.0's in a few big ways 💅
- Consent and authorization flexibility:
bestie, OAuth 2.0 basically assumes the user has a web browser 🌐 The type of interaction is locked in by the grant type, and the most common interactive grant types start in the browser. OAuth 2.0 also assumes that the person using the client software is the same person who approves access at the AS.
GNAP is way more flexible 🫶 It allows lots of patterns for managing the authorizations and consents needed for this requested delegation. That can include info sent by the client instance, info supplied by external parties, and info gathered during the interaction process. A client instance can list different ways it can start and finish an interaction, and those can be mixed together for different use cases. GNAP interactions can use a browser, but they don't have to 🙌 Methods can use inter-application messaging protocols, out-of-band data transfer, or literally anything else. Extensions can define new ways to start and finish an interaction, since new methods and platforms are expected to show up over time ✨ GNAP is designed so the end user and the RO can be two different people, and it still works when they're the same party too 💖
- Intent registration and inline negotiation:
OAuth 2.0 uses different "grant types" that start at different endpoints for different purposes 😵💫 Lots of these need discovery of several interrelated parameters.
GNAP requests all start with the same type of request to the same endpoint at the AS 🎀 Next steps get negotiated between the client instance and the AS based on software capabilities, policies around the requested access, and the overall context of the ongoing request. GNAP defines a continuation API that lets the client instance and AS request and send extra info to each other over multiple steps. This continuation API uses the same access token protection that other GNAP-protected APIs use 🔐 GNAP allows discovery to optimize the requests, but it isn't required thanks to the negotiation capabilities.
GNAP can handle the whole life cycle of an authorization request, so the mental model is way simpler than OAuth2 🧚 For instance, there's no need for refresh tokens when the API enables proper rotation of access tokens.
- Client instances:
OAuth 2.0 requires all clients to be registered at the AS and to use a client_id that the AS already knows as part of the protocol 🪪 That client_id is generally assumed to be assigned by a trusted authority during a registration process, so OAuth puts a lot of trust in the client_id ngl. Dynamic registration lets different classes of clients get a client_id at runtime, even if they only ever use it for one request.
GNAP lets the client instance present an unknown key to the AS and use that key to protect the ongoing request 🗝️ GNAP's client instance identifier mechanism lets pre-registered clients and dynamically registered clients exist as an optimized case, without needing the identifier as part of the protocol at all times 🌸
- Expanded delegation:
OAuth 2.0 defines the "scope" parameter for controlling access to APIs 🍓 This parameter has been coopted to mean a bunch of different things in different protocols, including flags for turning special behavior on and off and returning data apart from the access token. The "resource" indicator (defined in [RFC8707]) and Rich Authorization Request (RAR) extensions (as defined in [RFC9396]) expand on the "scope" concept in similar but different ways.
GNAP defines a rich structure for requesting access (analogous to RAR), with string references as an optimization (analogous to scopes) 🌈 GNAP also defines methods for requesting directly returned user information, separate from API access. This info includes identifiers for the current user and structured assertions. GNAP makes no assumptions or demands on the format or contents of the access token, but the RS extension allows a negotiation of token formats between the AS and RS 🦋
- Cryptography-based security:
OAuth 2.0 uses shared bearer secrets, including the client_secret and access token, and advanced authentication and sender constraints have been built on after the fact in inconsistent ways 😭
In GNAP, all communication between the client instance and AS is bound to a key held by the client instance 🔑 GNAP uses the same cryptographic mechanisms for both authenticating the client (to the AS) and binding the access token (to the RS and the AS) 💜 GNAP allows extensions to define new cryptographic protection mechanisms, since new methods are expected to show up over time. GNAP doesn't have the notion of "public clients" because key information can always be sent and used dynamically ✨
- Privacy and usable security:
OAuth 2.0's deployment model assumes a strong binding between the AS and the RS 🏠
GNAP is designed to be interoperable with decentralized identity standards and to provide a human-centric authorization layer 🫶 In addition to this specification, GNAP supports various patterns of communication between RSs and ASes through extensions. GNAP tries to limit the odds of consolidation down to just a handful of popular AS services 🌷
Appendix B. Example Protocol Flows 🧚
The protocol defined in this specification has a bunch of features that can be combined to solve lots of different kinds of authentication scenarios 💖 This section shows examples of how the protocol could be applied in different situations.
Some longer fields, particularly cryptographic information, have been truncated for display purposes in these examples ✂️
B.1. Redirect-Based User Interaction
In this scenario, the user is the RO and has access to a web browser, and the client instance can take front-channel callbacks on the same device as the user 🌸 This combo is analogous to the OAuth 2.0 Authorization Code grant type.
The client instance starts the request to the AS. Here, the client instance identifies itself using its public key 🔑
POST /tx HTTP/1.1
Host: server.example.com
Content-Type: application/json
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
{
"access_token": {
"access": [
{
"actions": [
"read",
"write",
"dolphin"
],
"locations": [
"https://server.example.net/",
"https://resource.local/other"
],
"datatypes": [
"metadata",
"images"
]
}
],
},
"client": {
"key": {
"proof": "httpsig",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "xyz-1",
"alg": "RS256",
"n": "kOB5rR4Jv0GMeLaY6_It_r3ORwdf8ci_JtffXyaSx8..."
}
}
},
"interact": {
"start": ["redirect"],
"finish": {
"method": "redirect",
"uri": "https://client.example.net/return/123455",
"nonce": "LKLTI25DK82FX4T4QFZC"
}
}
}
The AS processes the request and figures out that the RO needs to interact 👀 It returns the following response, which gives the client instance the info it needs to connect. The AS has also told the client instance that it can use the given instance identifier to identify itself in future requests (Section 2.3.1) 🪄
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"interact": {
"redirect":
"https://server.example.com/interact/4CF492MLVMSW9MKM",
"finish": "MBDOFXG4Y5CVJCX821LH"
}
"continue": {
"access_token": {
"value": "80UPRY5NM33OMUKMKSKU"
},
"uri": "https://server.example.com/continue"
},
"instance_id": "7C7C4AZ9KHRS6X63AJAO"
}
The client instance saves the response and redirects the user to the interaction start mode's "redirect" URI by sending the following HTTP message to the user's browser 🏃♀️💨
HTTP 303 Found
Location: https://server.example.com/interact/4CF492MLVMSW9MKM
The user's browser fetches the AS's interaction URI 🌐 The user logs in, gets identified as the RO for the resource being requested, and approves the request. Since the AS has a callback parameter that was sent in the initial request's interaction finish method, the AS generates the interaction reference, calculates the hash, and redirects the user back to the client instance with these extra values added as query parameters 🎀
NOTE: '' line wrapping per RFC 8792
HTTP 302 Found
Location: https://client.example.net/return/123455\
?hash=x-gguKWTj8rQf7d7i3w3UhzvuJ5bpOlKyAlVpLxBffY\
&interact_ref=4IFWWIKYBC2PQ6U56NL1
The client instance receives this request from the user's browser 📬 It makes sure this is the same user that was sent out by validating session information, and then it retrieves the stored pending request. The client instance uses the values in that to validate the hash parameter. Then it calls the continuation URI using the associated continuation access token and presents the interaction reference in the request content. The client instance signs the request as above ✍️
POST /continue HTTP/1.1
Host: server.example.com
Content-Type: application/json
Authorization: GNAP 80UPRY5NM33OMUKMKSKU
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
{
"interact_ref": "4IFWWIKYBC2PQ6U56NL1"
}
The AS retrieves the pending request by looking up the pending grant request associated with the presented continuation access token 🔍 Seeing that the grant is approved, the AS issues an access token and returns it to the client instance 🎉
NOTE: '' line wrapping per RFC 8792
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"access_token": {
"value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
"manage": "https://server.example.com/token/PRY5NM33O\
M4TB8N6BW7OZB8CDFONP219RP1L",
"access": [{
"actions": [
"read",
"write",
"dolphin"
],
"locations": [
"https://server.example.net/",
"https://resource.local/other"
],
"datatypes": [
"metadata",
"images"
]
}]
},
"continue": {
"access_token": {
"value": "80UPRY5NM33OMUKMKSKU"
},
"uri": "https://server.example.com/continue"
}
}
B.2. Secondary Device Interaction
okay so this one is giving smart TV with no browser 📺✨ the user doesn't have a web browser on the device, so they've gotta use a second device (phone, laptop, whatever) to talk to the AS 💅. the client instance can show a user code or a printable QR code 🫶. it can't receive callbacks from the AS, so it's stuck polling for updates while the user approves the request 🙈
the client instance kicks things off by sending the request to the AS:
POST /tx HTTP/1.1
Host: server.example.com
Content-Type: application/json
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
{
"access_token": {
"access": [
"dolphin-metadata", "some other thing"
],
},
"client": "7C7C4AZ9KHRS6X63AJAO",
"interact": {
"start": ["redirect", "user_code"]
}
}
the AS looks at this and realizes the RO needs to interact 👀. it supports both redirect URIs and user codes, so it sends back both 🎀. there's no interaction finish mode here, so no nonce 🙅♀️. BUT it does include a "wait" parameter on the continuation section, because it knows the client is gonna be polling for results 🔁
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"interact": {
"redirect": "https://srv.ex/MXKHQ",
"user_code": {
"code": "A1BC3DFF"
}
},
"continue": {
"access_token": {
"value": "80UPRY5NM33OMUKMKSKU"
},
"uri": "https://server.example.com/continue/VGJKPTKC50",
"wait": 60
}
}
the client instance saves that response, shows the user code on screen next to the static device URI 📱, and also shows the short interaction URI as a QR code for scanning 🌈.
now the user has two options 🦋:
- if they scan the code, they land on the interaction endpoint, and the AS finds the current pending request from the incoming URI 🔍
- if they go to the static page and type the code in by hand, the AS finds the pending request using the user code value ⌨️
either way, the user logs in, gets identified as the RO for the resource being asked for, and approves the request 💖. once it's approved, the AS shows them a message telling them to head back to their device 🙌
meanwhile, the client instance is polling the AS every 60 seconds at the continuation URI ⏰. it signs each request with the same key and method it used the first time 🔐
POST /continue/VGJKPTKC50 HTTP/1.1
Host: server.example.com
Authorization: GNAP 80UPRY5NM33OMUKMKSKU
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
the AS pulls up the pending request tied to the continuation access token and sees it hasn't been authorized yet 😤. so it tells the client no access token exists yet, but the client can keep calling after another 60-second timeout 🕰️
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"continue": {
"access_token": {
"value": "G7YQT4KQQ5TZY9SLSS5E"
},
"uri": "https://server.example.com/continue/ATWHO4Q1WV",
"wait": 60
}
}
heads up, the continuation URI and access token got rotated after that last call 🔄. so the client waits out the 60-second timeout and polls again using the new info 💅
POST /continue/ATWHO4Q1WV HTTP/1.1
Host: server.example.com
Authorization: GNAP G7YQT4KQQ5TZY9SLSS5E
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
this time the AS finds the pending request by the URI and access token, sees it's been approved 🎉, and issues an access token for the client to use at the RS 👑
NOTE: '' line wrapping per RFC 8792
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"access_token": {
"value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
"manage": "https://server.example.com/token/PRY5NM33O\
M4TB8N6BW7OZB8CDFONP219RP1L",
"access": [
"dolphin-metadata", "some other thing"
]
}
}
B.3. No User Involvement
this one's the no drama, no humans scenario 🤖🧚. the client instance is asking for access on its own behalf, and there's no user around to interact with at all 💜
the client instance builds a request to the AS, identifies itself with its public key, and uses MTLS to send it 🔑
POST /tx HTTP/1.1
Host: server.example.com
Content-Type: application/json
{
"access_token": {
"access": [
"backend service", "nightly-routine-3"
],
},
"client": {
"key": {
"proof": "mtls",
"cert#S256": "bwcK0esc3ACC3DB2Y5_lESsXE8o9ltc05O89jdN-dg2"
}
}
}
the AS checks it out, decides the client can ask for those resources 🧐, and hands over an access token 🎀
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"access_token": {
"value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
"manage": "https://server.example.com/token",
"access": [
"backend service", "nightly-routine-3"
]
}
}
B.4. Asynchronous Authorization
okay this one's a little more plot twist 🌀. the client instance is requesting on behalf of a specific RO, but it has no way to talk to the user directly 🙈. no problem though, because the AS can reach out to the RO asynchronously and ask them for approval 📲✨
the client starts by asking the AS for a set of resources 💌. it also names a particular user 👑
POST /tx HTTP/1.1
Host: server.example.com
Content-Type: application/json
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
{
"access_token": {
"access": [
{
"type": "photo-api",
"actions": [
"read",
"write",
"dolphin"
],
"locations": [
"https://server.example.net/",
"https://resource.local/other"
],
"datatypes": [
"metadata",
"images"
]
},
"read", "dolphin-metadata",
{
"type": "financial-transaction",
"actions": [
"withdraw"
],
"identifier": "account-14-32-32-3",
"currency": "USD"
},
"some other thing"
],
},
"client": "7C7C4AZ9KHRS6X63AJAO",
"user": {
"sub_ids": [ {
"format": "opaque",
"id": "J2G8G8O4AZ"
} ]
}
}
the AS reads this and realizes the RO needs to get involved 👀. it figures out it can reach that user asynchronously 📬, and that the user actually has the ability to approve this request 💅. so it tells the client it can poll for continuation 🔁
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"continue": {
"access_token": {
"value": "80UPRY5NM33OMUKMKSKU"
},
"uri": "https://server.example.com/continue",
"wait": 60
}
}
the AS reaches out to the RO and asks for consent 🔔. in this example, the AS has an app it can push notifications to for that account 📱🌸
meanwhile, the client instance keeps polling the AS every 60 seconds at the continuation URI ⏰
POST /continue HTTP/1.1
Host: server.example.com
Authorization: GNAP 80UPRY5NM33OMUKMKSKU
Signature-Input: sig1=...
Signature: sig1=...
the AS pulls up the pending request from the continuation access token and sees it still hasn't been authorized 😤. so it tells the client no access token has been issued yet, but it can keep calling after another 60-second timeout 🕰️
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"continue": {
"access_token": {
"value": "BI9QNW6V9W3XFJK4R02D"
},
"uri": "https://server.example.com/continue",
"wait": 60
}
}
the continuation access token value got rotated after it was used for that call 🔄. so the client waits out the 60-second timeout and polls again with the new token 💖
POST /continue HTTP/1.1
Host: server.example.com
Authorization: GNAP BI9QNW6V9W3XFJK4R02D
Signature-Input: sig1=...
Signature: sig1=...
this time the AS finds the pending request by the handle, sees it's been approved 🎉, and issues an access token 👑
NOTE: '' line wrapping per RFC 8792
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"access_token": {
"value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
"manage": "https://server.example.com/token/PRY5NM33O\
M4TB8N6BW7OZB8CDFONP219RP1L",
"access": [
"dolphin-metadata", "some other thing"
]
}
}
B.5. Applying OAuth 2.0 Scopes and Client IDs
okay so bestie, this one is SO cute 🎀 GNAP isn't built to be directly compatible with OAuth 2.0 [RFC6749], but the designers really did think about how to make OAuth 2.0 concepts and constructs slide into GNAP smoothly ✨ 💅
Picture this: the client developer already has a client_id and a set of scope values from their OAuth 2.0 system, and they want to bring them over to the new protocol 🦋 In OAuth 2.0, they'd stuff their client_id and scope values into parameters on a redirect to the authorization endpoint.
NOTE: '' line wrapping per RFC 8792
HTTP 302 Found
Location: https://server.example.com/authorize\
?client_id=7C7C4AZ9KHRS6X63AJAO\
&scope=read%20write%20dolphin\
&redirect_uri=https://client.example.net/return\
&response_type=code\
&state=123455
Now the developer wants to make an analogous request to the AS using GNAP 🌈 So the client instance makes an HTTP POST and drops the OAuth 2.0 values into the right spots 🍓
POST /tx HTTP/1.1
Host: server.example.com
Content-Type: application/json
Signature-Input: sig1=...
Signature: sig1=...
Content-Digest: sha-256=...
{
"access_token": {
"access": [
"read", "write", "dolphin"
],
"flags": [ "bearer" ]
},
"client": "7C7C4AZ9KHRS6X63AJAO",
"interact": {
"start": ["redirect"],
"finish": {
"method": "redirect",
"uri": "https://client.example.net/return?state=123455",
"nonce": "LKLTI25DK82FX4T4QFZC"
}
}
}
Look at how it all lines up 😭🫶 The client_id becomes the client value, which identifies the client instance's keys it uses for authentication. The scopes turn into the access array, representing the resources the client instance is requesting. And the redirect_uri and state value get pre-combined into a finish URI that can be unique per request 🧚 The client instance also makes a nonce to protect the callback, separate from the state parameter it tucked onto its return URI 🔐
From here, the protocol continues as above 💜
Appendix C. Interoperability Profiles ✨
The GNAP specification has so many modes, options, and mechanisms 🌸 It can solve a huge variety of problems in a huge variety of deployments, which is iconic, but it also makes it really hard, if not impossible, to define a set of mandatory-to-implement features. One environment's required feature could be totally impossible in another environment 💀 This is a big problem in lots of systems, but GNAP's back-and-forth negotiation saves the day 👑 Parties can declare at runtime everything they support, and then the other party picks the subset of items they also support. That gets you functional compatibility in many parts of the protocol, even in an open-world scenario 🦋
On top of that, GNAP defines a set of interoperability profiles 🎀 These gather together core requirements and fix options into common configurations that are likely to be useful to large populations of similar applications.
Conformant AS implementations of these profiles MUST implement at least the features as specified in the profile, and they MAY implement additional features or profiles. Conformant client implementations of these profiles MUST implement at least the features as specified, except where a subset of the features still lets the protocol function (like using polling instead of a push finish method for the Secondary Device profile) 🫶
C.1. Web-Based Redirection 🌈
Implementations conformant to the web-based redirection profile of GNAP MUST implement all of the following features:
- Interaction Start Methods: redirect
- Interaction Finish Methods: redirect
- Interaction Hash Algorithms: sha-256
- Key Proofing Methods: httpsig with no additional parameters
- Key Formats: jwks with signature algorithm included in the key's alg parameter
- JOSE Signature Algorithm: PS256
- Subject Identifier Formats: opaque
- Assertion Formats: id_token
C.2. Secondary Device 📱
Implementations conformant to the Secondary Device profile of GNAP MUST implement all of the following features:
- Interaction Start Methods: user_code and user_code_uri
- Interaction Finish Methods: push
- Interaction Hash Algorithms: sha-256
- Key Proofing Methods: httpsig with no additional parameters
- Key Formats: jwks with signature algorithm included in the key's alg parameter
- JOSE Signature Algorithm: PS256
- Subject Identifier Formats: opaque
- Assertion Formats: id_token
Appendix D. Guidance for Extensions 🧚
Extensions to this specification have a bunch of places they can change the protocol 💅 Lots of fields and objects can take additional values from a registry (Section 10) that this specification sets up. For interoperability, and to keep the protocol secure, extensions should register new values with IANA by following the mechanism that's specified 🔐 It might technically be possible to extend the protocol by adding elements to JSON objects that aren't governed by an IANA registry, but a recipient may ignore those values, and it's also allowed to reject them 🙈
Most object fields in GNAP are specified with types, and those types can allow different but related behavior ✨ For example, the access array can include either strings or objects, as discussed in Section 8. JSON polymorphism (Appendix E) lets extensions define new fields by choosing a new name, or by using an existing name with a new type 🌸 But the extension's definition of a new type for a field needs to fit the same kind of item being extended 👑 Say a hypothetical extension defined a string value for the access_token request field, with a URL to download a hosted access token request. That would be appropriate, because the access_token field still defines the access tokens being requested 🦋 But if an extension defined a string value for the access_token request field, and the value was something unrelated to the access token request, like a value or key format, that would NOT be an appropriate way to extend things 💀 (Heads up: this specific extension example would create another form of SSRF attack surface, as discussed in Section 11.34. 😭)
Another example: both interaction start modes (Section 2.5.1) and key proofing methods (Section 7.3) can be defined as either strings or objects 🎀 An extension could take a method defined as a string, like app, and define an object-based version with extra parameters. That extension should still define a method to launch an application on the end user's device, just like app does when it's specified as a string 🌈
Also, the ability to deal with different types for a field isn't expected to be equal between an AS and client software 💅 The client software is assumed to be both more varied and more simplified than the AS 🫶 And because of GNAP's negotiation process, the AS has more chance of recovering from unknown situations and parameters ✨ So any extension that changes the type of any field returned to a client instance should only do that when the client instance has specifically indicated support for that extension through some kind of request parameter 💖
Appendix E. JSON Structures and Polymorphism 💜
GNAP uses polymorphism within the JSON [RFC8259] structures used for the protocol 🦋 Each portion of this protocol is defined in terms of the JSON data type its values can take, whether that's a string, object, array, boolean, or number 🌸 For some fields, different data types offer different descriptive capabilities and get used in different situations for the same field 🎀 Each data type gives a different syntax for the same underlying semantic protocol element, which allows for optimization and simplification in lots of common cases 🍓
Even though JSON is often used to describe strongly typed structures, JSON on its own is naturally polymorphic 🧚 In JSON, the named members of an object have no type attached to them, and any data type can be the value for any member. In practice, each member has a semantic type that needs to make sense to the parties creating and consuming the object 💅 Within this protocol, each object member is defined in terms of its semantic content, and that content might be expressed in different concrete data types for different specific purposes ✨ Since each object member has exactly one value in JSON, each data type for an object member field is naturally mutually exclusive with the other data types within a single JSON object 🫶
For example, a resource request for a single access token is an object of resource request descriptions, while a request for multiple access tokens is an array whose member values are all objects 👑 Both represent requests for access, but the difference in syntax lets the client instance and AS tell the two request types apart in the same request 🦋
Another form of polymorphism in JSON comes from the fact that values inside JSON arrays don't all have to be the same JSON data type 🌈 Within this protocol, though, each element of the array needs to be the same kind of semantic element for the collection to make sense, even when the data types differ from each other 💖
For example, each aspect of a resource request can be described using an object with multiple dimensional components, or the aspect can just be requested using a string 🎀 In both cases the resource request is described in a way the AS needs to interpret, just with different levels of specificity and complexity for the client instance to deal with 🌸 An API designer can offer a set of common access scopes as simple strings, but still let client software developers specify custom access when they need it for more complex APIs 🍓
Extensions to this specification can use different data types for defined fields, but each extension needs to not only declare what the data type means, but also justify why the data type represents the same basic kind of thing it extends 🧚 For example, an extension declaring an "array" representation for a field would need to explain how the array represents something akin to the non-array element it's replacing 💅 See additional discussion in Appendix D 🫶
Acknowledgements ✨
Okay bestie, the authors gave a massive shoutout to everyone who helped with their reviews, implementations, and contributions 💖. The list is Åke Axeland, Aaron Parecki, Adam Omar Oueidat, Andrii Deinega, Annabelle Backman, Dick Hardt, Dmitri Zagidulin, Dmitry Barinov, Florian Helmschmidt, Francis Pouatcha, George Fletcher, Haardik Haardik, Hamid Massaoud, Jacky Yuan, Joseph Heenan, Kathleen Moriarty, Leif Johansson, Mike Jones, Mike Varley, Nat Sakimura, Takahiko Kawasaki, Takahiro Tsuchiya, and Yaron Sheffer 🫶 literally a whole squad.
The GNAP Working Group design team also gets flowers 🌸. That's Kathleen Moriarty, Dick Hardt, Mike Jones, and the authors, and they're the ones who mixed in bits of the XAuth and XYZ proposals to make the very first draft of this document. Iconic teamwork 👑.
Also, the authors gave a special thank-you to Aaron Parecki and Mike Jones for their insights on how to fold identity and authentication systems into the core protocol 🧚. Justin Richer and Dick Hardt came up with the use cases, diagrams, and insights from the XYZ and XAuth proposals that made it into here, and the authors really wanted to single out Mike Varley and the team at SecureKey 🦋. They gave feedback and built early versions of the XYZ protocol, which fed right into this standards work. No cap, that's a real glow-up pipeline.
And finally, the authors want to acknowledge the immense contributions of Aaron Parecki to the content of this document. They thank him for his insight, input, and hard work, and honestly? GNAP would not have become what it is without him 😭💜.
Authors' Addresses
Okay, here are the authors and how to reach them 💌:
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: rfc9635 2024-10
- 💖 girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms