oauth2_core/
access_token_request.rs

1//! https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
2//! https://datatracker.ietf.org/doc/html/rfc6749#section-4.3.2
3//! https://datatracker.ietf.org/doc/html/rfc6749#section-4.4.2
4//! https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
5//! https://datatracker.ietf.org/doc/html/rfc7523#section-2.1
6
7use http::Method;
8use mime::Mime;
9use serde::{Deserialize, Serialize};
10use serde_json::{Map, Value};
11
12use crate::types::{
13    ClientId, ClientPassword, ClientSecret, Code, CodeVerifier, Scope, ScopeFromStrError,
14    ScopeParameter,
15};
16
17pub const METHOD: Method = Method::POST;
18pub const CONTENT_TYPE: Mime = mime::APPLICATION_WWW_FORM_URLENCODED;
19pub const GRANT_TYPE_WITH_AUTHORIZATION_CODE_GRANT: &str = "authorization_code";
20
21//
22//
23//
24#[derive(Serialize, Deserialize, Debug, Clone)]
25#[serde(tag = "grant_type")]
26pub enum Body<SCOPE>
27where
28    SCOPE: Scope,
29{
30    /// https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
31    #[serde(rename = "authorization_code")]
32    AuthorizationCodeGrant(BodyWithAuthorizationCodeGrant),
33    /// https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
34    #[serde(rename = "urn:ietf:params:oauth:grant-type:device_code")]
35    DeviceAuthorizationGrant(BodyWithDeviceAuthorizationGrant),
36    /// https://datatracker.ietf.org/doc/html/rfc6749#section-4.4
37    #[serde(rename = "client_credentials")]
38    ClientCredentialsGrant(BodyWithClientCredentialsGrant<SCOPE>),
39    #[serde(rename = "password")]
40    ResourceOwnerPasswordCredentialsGrant(BodyWithResourceOwnerPasswordCredentialsGrant<SCOPE>),
41    /// https://datatracker.ietf.org/doc/html/rfc7523#section-2.1
42    #[serde(rename = "urn:ietf:params:oauth:grant-type:jwt-bearer")]
43    JwtAuthorizationGrant(BodyWithJwtAuthorizationGrant<SCOPE>),
44}
45
46//
47#[derive(Serialize, Deserialize, Debug, Clone)]
48pub struct BodyWithAuthorizationCodeGrant {
49    pub code: Code,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub redirect_uri: Option<String>,
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub client_id: Option<ClientId>,
54    // Note: Not in rfc6749, but usually need.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub client_secret: Option<ClientSecret>,
57
58    // PKCE
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub code_verifier: Option<CodeVerifier>,
61
62    #[serde(flatten, skip_serializing_if = "Option::is_none")]
63    _extra: Option<Map<String, Value>>,
64}
65
66impl BodyWithAuthorizationCodeGrant {
67    pub fn new(
68        code: Code,
69        redirect_uri: Option<String>,
70        client_id: Option<ClientId>,
71        client_secret: Option<ClientSecret>,
72    ) -> Self {
73        Self::internal_new(code, redirect_uri, client_id, client_secret, None)
74    }
75
76    fn internal_new(
77        code: Code,
78        redirect_uri: Option<String>,
79        client_id: Option<ClientId>,
80        client_secret: Option<ClientSecret>,
81        code_verifier: Option<CodeVerifier>,
82    ) -> Self {
83        Self {
84            code,
85            redirect_uri,
86            client_id,
87            client_secret,
88            code_verifier,
89            _extra: None,
90        }
91    }
92
93    pub fn set_extra(&mut self, extra: Map<String, Value>) {
94        self._extra = Some(extra);
95    }
96    pub fn extra(&self) -> Option<&Map<String, Value>> {
97        self._extra.as_ref()
98    }
99}
100
101//
102#[derive(Serialize, Deserialize, Debug, Clone)]
103pub struct BodyWithDeviceAuthorizationGrant {
104    pub device_code: String,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub client_id: Option<ClientId>,
107    // Note: Not in rfc6749, but may need.
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub client_secret: Option<ClientSecret>,
110
111    #[serde(flatten, skip_serializing_if = "Option::is_none")]
112    _extra: Option<Map<String, Value>>,
113}
114
115impl BodyWithDeviceAuthorizationGrant {
116    pub fn new(
117        device_code: String,
118        client_id: Option<ClientId>,
119        client_secret: Option<ClientSecret>,
120    ) -> Self {
121        Self {
122            device_code,
123            client_id,
124            client_secret,
125            _extra: None,
126        }
127    }
128
129    pub fn set_extra(&mut self, extra: Map<String, Value>) {
130        self._extra = Some(extra);
131    }
132    pub fn extra(&self) -> Option<&Map<String, Value>> {
133        self._extra.as_ref()
134    }
135}
136
137//
138#[derive(Serialize, Deserialize, Debug, Clone)]
139pub struct BodyWithClientCredentialsGrant<SCOPE>
140where
141    SCOPE: Scope,
142{
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub scope: Option<ScopeParameter<SCOPE>>,
145    #[serde(flatten, skip_serializing_if = "Option::is_none")]
146    pub client_password: Option<ClientPassword>,
147
148    #[serde(flatten, skip_serializing_if = "Option::is_none")]
149    _extra: Option<Map<String, Value>>,
150}
151
152impl<SCOPE> BodyWithClientCredentialsGrant<SCOPE>
153where
154    SCOPE: Scope,
155{
156    pub fn new(scope: Option<ScopeParameter<SCOPE>>) -> Self {
157        Self {
158            scope,
159            client_password: None,
160            _extra: None,
161        }
162    }
163
164    pub fn new_with_client_password(
165        scope: Option<ScopeParameter<SCOPE>>,
166        client_password: ClientPassword,
167    ) -> Self {
168        Self {
169            scope,
170            client_password: Some(client_password),
171            _extra: None,
172        }
173    }
174
175    pub fn set_extra(&mut self, extra: Map<String, Value>) {
176        self._extra = Some(extra);
177    }
178    pub fn extra(&self) -> Option<&Map<String, Value>> {
179        self._extra.as_ref()
180    }
181
182    pub fn try_from_t_with_string(
183        body: &BodyWithClientCredentialsGrant<String>,
184    ) -> Result<Self, ScopeFromStrError> {
185        let scope = if let Some(x) = &body.scope {
186            Some(ScopeParameter::<SCOPE>::try_from_t_with_string(x)?)
187        } else {
188            None
189        };
190
191        let mut this = Self::new(scope);
192        this.client_password = body.client_password.to_owned();
193        if let Some(extra) = body.extra() {
194            this.set_extra(extra.to_owned());
195        }
196        Ok(this)
197    }
198}
199
200//
201#[derive(Serialize, Deserialize, Debug, Clone)]
202pub struct BodyWithResourceOwnerPasswordCredentialsGrant<SCOPE>
203where
204    SCOPE: Scope,
205{
206    pub username: String,
207    pub password: String,
208
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub scope: Option<ScopeParameter<SCOPE>>,
211    #[serde(flatten, skip_serializing_if = "Option::is_none")]
212    pub client_password: Option<ClientPassword>,
213
214    #[serde(flatten, skip_serializing_if = "Option::is_none")]
215    _extra: Option<Map<String, Value>>,
216}
217
218impl<SCOPE> BodyWithResourceOwnerPasswordCredentialsGrant<SCOPE>
219where
220    SCOPE: Scope,
221{
222    pub fn new(
223        username: impl AsRef<str>,
224        password: impl AsRef<str>,
225        scope: Option<ScopeParameter<SCOPE>>,
226    ) -> Self {
227        Self {
228            username: username.as_ref().to_owned(),
229            password: password.as_ref().to_owned(),
230            scope,
231            client_password: None,
232            _extra: None,
233        }
234    }
235
236    pub fn new_with_client_password(
237        username: impl AsRef<str>,
238        password: impl AsRef<str>,
239        scope: Option<ScopeParameter<SCOPE>>,
240        client_password: ClientPassword,
241    ) -> Self {
242        Self {
243            username: username.as_ref().to_owned(),
244            password: password.as_ref().to_owned(),
245            scope,
246            client_password: Some(client_password),
247            _extra: None,
248        }
249    }
250
251    pub fn set_extra(&mut self, extra: Map<String, Value>) {
252        self._extra = Some(extra);
253    }
254    pub fn extra(&self) -> Option<&Map<String, Value>> {
255        self._extra.as_ref()
256    }
257
258    pub fn try_from_t_with_string(
259        body: &BodyWithResourceOwnerPasswordCredentialsGrant<String>,
260    ) -> Result<Self, ScopeFromStrError> {
261        let scope = if let Some(x) = &body.scope {
262            Some(ScopeParameter::<SCOPE>::try_from_t_with_string(x)?)
263        } else {
264            None
265        };
266
267        let mut this = Self::new(&body.username, &body.password, scope);
268        this.client_password = body.client_password.to_owned();
269        if let Some(extra) = body.extra() {
270            this.set_extra(extra.to_owned());
271        }
272        Ok(this)
273    }
274}
275
276//
277#[derive(Serialize, Deserialize, Debug, Clone)]
278pub struct BodyWithJwtAuthorizationGrant<SCOPE>
279where
280    SCOPE: Scope,
281{
282    pub assertion: String,
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub scope: Option<ScopeParameter<SCOPE>>,
285    #[serde(skip_serializing_if = "Option::is_none")]
286    pub client_id: Option<ClientId>,
287
288    #[serde(flatten, skip_serializing_if = "Option::is_none")]
289    _extra: Option<Map<String, Value>>,
290}
291
292impl<SCOPE> BodyWithJwtAuthorizationGrant<SCOPE>
293where
294    SCOPE: Scope,
295{
296    pub fn new(
297        assertion: String,
298        scope: Option<ScopeParameter<SCOPE>>,
299        client_id: Option<ClientId>,
300    ) -> Self {
301        Self {
302            assertion,
303            scope,
304            client_id,
305            _extra: None,
306        }
307    }
308
309    pub fn set_extra(&mut self, extra: Map<String, Value>) {
310        self._extra = Some(extra);
311    }
312    pub fn extra(&self) -> Option<&Map<String, Value>> {
313        self._extra.as_ref()
314    }
315
316    pub fn try_from_t_with_string(
317        body: &BodyWithJwtAuthorizationGrant<String>,
318    ) -> Result<Self, ScopeFromStrError> {
319        let scope = if let Some(x) = &body.scope {
320            Some(ScopeParameter::<SCOPE>::try_from_t_with_string(x)?)
321        } else {
322            None
323        };
324
325        let mut this = Self::new(body.assertion.to_owned(), scope, body.client_id.to_owned());
326
327        if let Some(extra) = body.extra() {
328            this.set_extra(extra.to_owned());
329        }
330        Ok(this)
331    }
332}
333
334#[cfg(test)]
335mod tests_with_authorization_code_grant {
336    use super::*;
337
338    #[test]
339    fn test_ser_de() {
340        let body_str = "grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb";
341        match serde_urlencoded::from_str::<Body<String>>(body_str) {
342            Ok(Body::AuthorizationCodeGrant(body)) => {
343                assert_eq!(body.code, "SplxlOBeZQQYbYS6WxSbIA");
344                assert_eq!(
345                    body.redirect_uri,
346                    Some("https://client.example.com/cb".parse().unwrap())
347                );
348            }
349            #[allow(unreachable_patterns)]
350            Ok(body) => panic!("{body:?}"),
351            Err(err) => panic!("{err}"),
352        }
353    }
354}
355
356#[cfg(test)]
357mod tests_with_device_authorization_grant {
358    use super::*;
359
360    #[test]
361    fn test_ser_de() {
362        let body_str = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS&client_id=1406020730";
363        match serde_urlencoded::from_str::<Body<String>>(body_str) {
364            Ok(Body::DeviceAuthorizationGrant(body)) => {
365                assert_eq!(
366                    body.device_code,
367                    "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS"
368                );
369                assert_eq!(body.client_id, Some("1406020730".to_owned()));
370
371                assert_eq!(
372                    body_str,
373                    serde_urlencoded::to_string(Body::<String>::DeviceAuthorizationGrant(body))
374                        .unwrap()
375                );
376            }
377            #[allow(unreachable_patterns)]
378            Ok(body) => panic!("{body:?}"),
379            Err(err) => panic!("{err}"),
380        }
381    }
382
383    #[test]
384    fn test_ser_de_extra() {
385        //
386        let mut extra = Map::new();
387        extra.insert("foo".to_owned(), Value::String("bar".to_owned()));
388        let mut body = BodyWithDeviceAuthorizationGrant::new(
389            "your_device_code".to_owned(),
390            Some("your_client_id".to_owned()),
391            Some("your_client_secret".to_owned()),
392        );
393        body.set_extra(extra.to_owned());
394        let body = Body::<String>::DeviceAuthorizationGrant(body);
395        let body_str = serde_urlencoded::to_string(body).unwrap();
396        assert_eq!(body_str, "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&device_code=your_device_code&client_id=your_client_id&client_secret=your_client_secret&foo=bar");
397
398        match serde_urlencoded::from_str::<Body<String>>(body_str.as_str()) {
399            Ok(Body::DeviceAuthorizationGrant(body)) => {
400                assert_eq!(body.extra(), Some(&extra));
401            }
402            #[allow(unreachable_patterns)]
403            Ok(body) => panic!("{body:?}"),
404            Err(err) => panic!("{err}"),
405        }
406    }
407}
408
409#[cfg(test)]
410mod tests_with_client_credentials_grant {
411    use super::*;
412
413    #[test]
414    fn test_ser_de() {
415        let body_str = "grant_type=client_credentials";
416        match serde_urlencoded::from_str::<Body<String>>(body_str) {
417            Ok(Body::ClientCredentialsGrant(body)) => {
418                assert_eq!(body.client_password, None);
419            }
420            #[allow(unreachable_patterns)]
421            Ok(body) => panic!("{body:?}"),
422            Err(err) => panic!("{err}"),
423        }
424
425        let body_str =
426            "grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET";
427        match serde_urlencoded::from_str::<Body<String>>(body_str) {
428            Ok(Body::ClientCredentialsGrant(body)) => {
429                assert_eq!(body.client_password.unwrap().client_id, "CLIENT_ID");
430            }
431            #[allow(unreachable_patterns)]
432            Ok(body) => panic!("{body:?}"),
433            Err(err) => panic!("{err}"),
434        }
435
436        let body_str = "grant_type=client_credentials&client_id=CLIENT_ID";
437        match serde_urlencoded::from_str::<Body<String>>(body_str) {
438            Ok(Body::ClientCredentialsGrant(body)) => {
439                assert_eq!(body.client_password, None);
440            }
441            #[allow(unreachable_patterns)]
442            Ok(body) => panic!("{body:?}"),
443            Err(err) => panic!("{err}"),
444        }
445    }
446
447    #[test]
448    fn test_ser_de_extra() {
449        let body_str = "grant_type=client_credentials&foo=bar";
450        match serde_urlencoded::from_str::<Body<String>>(body_str) {
451            Ok(Body::ClientCredentialsGrant(body)) => {
452                assert_eq!(body.client_password, None);
453                assert_eq!(
454                    body.extra().unwrap().get("foo").unwrap().as_str(),
455                    Some("bar")
456                )
457            }
458            #[allow(unreachable_patterns)]
459            Ok(body) => panic!("{body:?}"),
460            Err(err) => panic!("{err}"),
461        }
462
463        let body_str =
464            "grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&foo=bar";
465        match serde_urlencoded::from_str::<Body<String>>(body_str) {
466            Ok(Body::ClientCredentialsGrant(body)) => {
467                assert_eq!(
468                    body.client_password.to_owned().unwrap().client_id,
469                    "CLIENT_ID"
470                );
471                assert_eq!(
472                    body.extra().unwrap().get("foo").unwrap().as_str(),
473                    Some("bar")
474                )
475            }
476            #[allow(unreachable_patterns)]
477            Ok(body) => panic!("{body:?}"),
478            Err(err) => panic!("{err}"),
479        }
480    }
481}
482
483#[cfg(test)]
484mod tests_with_resource_owner_password_credentials_grant {
485    use super::*;
486
487    #[test]
488    fn test_ser_de() {
489        let body_str = "grant_type=password&username=USERNAME&password=PASSWORD";
490        match serde_urlencoded::from_str::<Body<String>>(body_str) {
491            Ok(Body::ResourceOwnerPasswordCredentialsGrant(body)) => {
492                assert_eq!(body.username, "USERNAME");
493                assert_eq!(body.password, "PASSWORD");
494                assert_eq!(body.client_password, None);
495            }
496            #[allow(unreachable_patterns)]
497            Ok(body) => panic!("{body:?}"),
498            Err(err) => panic!("{err}"),
499        }
500
501        let body_str =
502            "grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID&client_secret=CLIENT_SECRET";
503        match serde_urlencoded::from_str::<Body<String>>(body_str) {
504            Ok(Body::ResourceOwnerPasswordCredentialsGrant(body)) => {
505                assert_eq!(body.username, "USERNAME");
506                assert_eq!(body.password, "PASSWORD");
507                assert_eq!(body.client_password.unwrap().client_id, "CLIENT_ID");
508            }
509            #[allow(unreachable_patterns)]
510            Ok(body) => panic!("{body:?}"),
511            Err(err) => panic!("{err}"),
512        }
513
514        let body_str =
515            "grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID";
516        match serde_urlencoded::from_str::<Body<String>>(body_str) {
517            Ok(Body::ResourceOwnerPasswordCredentialsGrant(body)) => {
518                assert_eq!(body.username, "USERNAME");
519                assert_eq!(body.password, "PASSWORD");
520                assert_eq!(body.client_password, None);
521            }
522            #[allow(unreachable_patterns)]
523            Ok(body) => panic!("{body:?}"),
524            Err(err) => panic!("{err}"),
525        }
526    }
527}
528
529#[cfg(test)]
530mod tests_with_jwt_authorization_grant {
531    use super::*;
532
533    #[test]
534    fn test_ser_de() {
535        let body_str = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
536        match serde_urlencoded::from_str::<Body<String>>(body_str) {
537            Ok(Body::JwtAuthorizationGrant(body)) => {
538                assert_eq!(
539                    body.assertion,
540                    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
541                );
542                assert_eq!(body.scope, None);
543                assert_eq!(body.client_id, None);
544
545                assert_eq!(
546                    body_str,
547                    serde_urlencoded::to_string(Body::<String>::JwtAuthorizationGrant(body))
548                        .unwrap()
549                );
550            }
551            #[allow(unreachable_patterns)]
552            Ok(body) => panic!("{body:?}"),
553            Err(err) => panic!("{err}"),
554        }
555    }
556}