ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
oauth2.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file oauth2.h
6 * @brief OAuth 2.0 client at the token endpoint (RFC 6749), PROTOCORE_ENABLE_OAUTH2.
7 *
8 * RFC 6749 sec 3.2 names the token endpoint; sec 4.1.3 is the Access Token Request that trades an
9 * authorization code for tokens, and sec 6 is the same exchange presenting a refresh token. Both
10 * requests are `application/x-www-form-urlencoded` bodies encoded per RFC 6749 Appendix B, whose
11 * percent-encoding is RFC 3986 sec 2.1 over the unreserved set of RFC 3986 sec 2.3. The reply is the
12 * JSON of RFC 6749 sec 5.1 on success, or the error object of sec 5.2, read here with the library's
13 * zero-heap JSON reader. `token_type` is "Bearer" for the tokens of RFC 6750 sec 6.1.1, which
14 * RFC 6750 sec 2.1 presents in the Authorization header.
15 *
16 * Two layers, one handle:
17 *
18 * - Host-testable core: @ref Oauth2Ns::build_code_request, @ref Oauth2Ns::build_refresh_request and
19 * @ref Oauth2Ns::parse_token_response work on caller-owned buffers.
20 * - Transport (needs PROTOCORE_ENABLE_HTTP_CLIENT): @ref Oauth2Ns::exchange_code and
21 * @ref Oauth2Ns::refresh post the built body to @c request.token_endpoint over the platform's
22 * HTTP(S) client and parse what comes back.
23 *
24 * A confidential client sets @c client.client_secret; a public client sets
25 * @c code_grant.code_verifier, the PKCE verifier of RFC 7636 sec 4.1 that sec 4.5 sends to the token
26 * endpoint. The other stays NULL. No heap, no stdlib.
27 *
28 * @author Douglas Quigg (dstroy0)
29 * @date 2026
30 */
31
32#ifndef PROTOCORE_OAUTH2_H
33#define PROTOCORE_OAUTH2_H
34
35#include "protocore_config.h" // the entry point: protocore_types.h for the widths
36
37#if PROTOCORE_ENABLE_OAUTH2
38
40
41#ifndef PROTOCORE_OAUTH2_TOKEN_TYPE_LEN
42#define PROTOCORE_OAUTH2_TOKEN_TYPE_LEN 24 ///< the `token_type` value buffer, "Bearer" and any longer registered name.
43#endif
44
45/** @brief RFC 6749 sec 5.1 access token response parameters. An absent one reads empty or 0. */
46typedef struct
47{
48 char access_token[PROTOCORE_OAUTH2_TOKEN_LEN]; ///< the issued access token, presented per RFC 6750 sec 2.1
49 char id_token[PROTOCORE_OAUTH2_TOKEN_LEN]; ///< the OpenID Connect Core 1.0 ID Token, an OpenID Foundation
50 ///< specification and not an RFC 6749 sec 5.1 parameter; verify it
51 ///< with services/security/oidc
52 char refresh_token[PROTOCORE_OAUTH2_RT_LEN]; ///< the refresh token a later sec 6 request presents
53 char token_type[PROTOCORE_OAUTH2_TOKEN_TYPE_LEN]; ///< the token type, "Bearer" per RFC 6750 sec 6.1.1
54 long expires_in; ///< the access token lifetime in seconds, 0 when absent
55} Oauth2Tokens;
56
57/** @brief Negative outcomes of a transport call. An HTTP status code is positive. */
58typedef enum PROTO_ENUM_PACKED
59{
60 PROTOCORE_OAUTH2_ERR_BUILD = -1, ///< the encoded body did not fit @c request.cap
61 PROTOCORE_OAUTH2_ERR_TRANSPORT = -2, ///< the HTTP client reached no endpoint
62 PROTOCORE_OAUTH2_ERR_RESPONSE = -3, ///< the body carried no access_token (RFC 6749 sec 5.2)
63} Oauth2Result;
64
65/**
66 * @brief RFC 6749 sec 3.2.1: the client identity a token request carries.
67 *
68 * A set @c client_secret is emitted as the sec 2.3.1 client password in the request body, the form
69 * that section marks NOT RECOMMENDED beside HTTP Basic.
70 */
71typedef struct
72{
73 const char *client_id; ///< the client identifier, required when the client does not authenticate (sec 4.1.3)
74 const char *client_secret; ///< the confidential client's secret (sec 2.3.1), or NULL for a public client
75} Oauth2ClientArgs;
76
77/** @brief RFC 6749 sec 4.1.3 authorization_code grant, plus the PKCE verifier RFC 7636 sec 4.5 sends. */
78typedef struct
79{
80 const char *code; ///< the authorization code received from the authorization server
81 const char *redirect_uri; ///< the redirection URI of the sec 4.1.1 authorization request, identical to it
82 const char *code_verifier; ///< the PKCE code verifier (RFC 7636 sec 4.1), or NULL
83} Oauth2CodeGrantArgs;
84
85/** @brief RFC 6749 sec 6: what refreshing an access token presents. */
86typedef struct
87{
88 const char *refresh_token; ///< the refresh token issued alongside the access token (sec 5.1)
89} Oauth2RefreshArgs;
90
91/** @brief The endpoint a request goes to (RFC 6749 sec 3.2) and the form body it is built into. */
92typedef struct
93{
94 const char *token_endpoint; ///< the token endpoint URI a transport call posts to
95 char *out; ///< where a build writes the Appendix B encoded body
96 size_t cap; ///< how much room that has, the NUL included
97} Oauth2RequestArgs;
98
99/** @brief The token-endpoint reply (RFC 6749 sec 5.1 / 5.2) and where its parameters land. */
100typedef struct
101{
102 const char *json; ///< the response body a parse reads
103 Oauth2Tokens *tokens; ///< where the parsed parameters are written
104} Oauth2ResponseArgs;
105
106/**
107 * @brief The token-endpoint client: two grants out, one token response back.
108 *
109 * A caller sets the members a call takes, invokes it through ::Oauth2, and reads the outcome off the
110 * same handle.
111 *
112 * @var Oauth2Ns::client the client identity a request carries (RFC 6749 sec 3.2.1)
113 * @var Oauth2Ns::code_grant the authorization_code grant's parameters (sec 4.1.3, RFC 7636 sec 4.5)
114 * @var Oauth2Ns::refresh_grant the refresh token a sec 6 request presents
115 * @var Oauth2Ns::request the token endpoint (sec 3.2) and the buffer a build encodes into
116 * @var Oauth2Ns::response the reply text a parse reads and the tokens it fills (sec 5.1)
117 * @var Oauth2Ns::ok the parse found an access_token, so the reply is a sec 5.1 response
118 * @var Oauth2Ns::i32 bytes a build wrote excluding the NUL and 0 when it did not fit, or a
119 * transport call's HTTP status and a negative ::Oauth2Result below it
120 * @var Oauth2Ns::build_code_request encode the sec 4.1.3 Access Token Request body
121 * @var Oauth2Ns::build_refresh_request encode the sec 6 refresh request body
122 * @var Oauth2Ns::parse_token_response read the sec 5.1 parameters out of @c response.json
123 * @var Oauth2Ns::exchange_code post the sec 4.1.3 body to the endpoint and parse the sec 4.1.4 reply
124 * @var Oauth2Ns::refresh post the sec 6 body to the endpoint and parse the reply
125 *
126 * A transport call points @c request.out and @c request.cap at the module's own body buffer and
127 * @c response.json at its own response buffer, so a caller sets only @c request.token_endpoint and
128 * @c response.tokens for those two.
129 *
130 * No storage without PROTOCORE_ENABLE_HTTP_CLIENT: the three core calls write the caller's buffers
131 * and keep nothing, and the body and reply buffers exist only for the two transport calls.
132 */
133typedef struct
134{
135 Oauth2ClientArgs client; ///< who the client is
136 Oauth2CodeGrantArgs code_grant; ///< the authorization_code grant
137 Oauth2RefreshArgs refresh_grant; ///< the refresh_token grant
138 Oauth2RequestArgs request; ///< where the request goes and where its body is built
139 Oauth2ResponseArgs response; ///< the reply and the tokens read out of it
140 proto_bool ok;
141 int32_t i32;
142#if PROTOCORE_ENABLE_HTTP_CLIENT
143#endif
144} Oauth2Vars;
145
146/** @brief The operands and the outcome. */
147extern Oauth2Vars Oauth2V;
148
149/** @brief The entries. */
150typedef struct
151{
152 void (*const build_code_request)(uint8_t *work);
153 void (*const build_refresh_request)(uint8_t *work);
154 void (*const parse_token_response)(uint8_t *work);
155 void (*const exchange_code)(uint8_t *work);
156 void (*const refresh)(uint8_t *work);
157} Oauth2Ns;
158
159// What the table binds, defined once in the .c and taking one parameter each: everything
160// else an entry needs is an operand in Oauth2V or a region of the borrow at a fixed offset.
161void protocore_oauth2_build_code_request(uint8_t *work);
162void protocore_oauth2_build_refresh_request(uint8_t *work);
163void protocore_oauth2_parse_token_response(uint8_t *work);
164void protocore_oauth2_exchange_code(uint8_t *work);
165void protocore_oauth2_refresh(uint8_t *work);
166
167// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
168// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
169// `Oauth2.build_code_request(work)` resolves to a named function and becomes a DIRECT call. An extern table
170// leaves the call indirect and the symbol live at every level, -O2 -flto included.
171static const Oauth2Ns Oauth2 __attribute__((unused)) = {
172 .build_code_request = protocore_oauth2_build_code_request,
173 .build_refresh_request = protocore_oauth2_build_refresh_request,
174 .parse_token_response = protocore_oauth2_parse_token_response,
175 .exchange_code = protocore_oauth2_exchange_code,
176 .refresh = protocore_oauth2_refresh,
177};
178
179/**
180 * @brief The PROTOCORE_OAUTH2_BORROW bytes this module's state lives in.
181 *
182 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
183 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
184 * walks, so the state lasts the life of the program.
185 *
186 * @return the span.
187 */
188#if PROTOCORE_ENABLE_HTTP_CLIENT
189uint8_t *protocore_oauth2_span(void);
190#endif // PROTOCORE_ENABLE_HTTP_CLIENT
191
193
194#endif // PROTOCORE_ENABLE_OAUTH2
195
196#endif // PROTOCORE_OAUTH2_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97