1 /***
2  * Copyright (C) Microsoft. All rights reserved.
3  * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
4  *
5  * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
6  *
7  * HTTP Library: Oauth 2.0
8  *
9  * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk
10  *
11  * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
12  ****/
13 #pragma once
14 
15 #ifndef CASA_OAUTH2_H
16 #define CASA_OAUTH2_H
17 
18 #include "cpprest/details/web_utilities.h"
19 #include "cpprest/http_msg.h"
20 
21 namespace web
22 {
23 namespace http
24 {
25 namespace client
26 {
27 // Forward declaration to avoid circular include dependency.
28 class http_client_config;
29 } // namespace client
30 
31 /// oAuth 2.0 library.
32 namespace oauth2
33 {
34 namespace details
35 {
36 class oauth2_handler;
37 
38 // Constant strings for OAuth 2.0.
39 typedef utility::string_t oauth2_string;
40 class oauth2_strings
41 {
42 public:
43 #define _OAUTH2_STRINGS
44 #define DAT(a_, b_) _ASYNCRTIMP static const oauth2_string a_;
45 #include "cpprest/details/http_constants.dat"
46 #undef _OAUTH2_STRINGS
47 #undef DAT
48 };
49 
50 } // namespace details
51 
52 /// oAuth functionality is currently in beta.
53 namespace experimental
54 {
55 /// <summary>
56 /// Exception type for OAuth 2.0 errors.
57 /// </summary>
58 class oauth2_exception : public std::exception
59 {
60 public:
oauth2_exception(utility::string_t msg)61     oauth2_exception(utility::string_t msg) : m_msg(utility::conversions::to_utf8string(std::move(msg))) {}
~oauth2_exception()62     ~oauth2_exception() CPPREST_NOEXCEPT {}
what()63     const char* what() const CPPREST_NOEXCEPT { return m_msg.c_str(); }
64 
65 private:
66     std::string m_msg;
67 };
68 
69 /// <summary>
70 /// OAuth 2.0 token and associated information.
71 /// </summary>
72 class oauth2_token
73 {
74 public:
75     /// <summary>
76     /// Value for undefined expiration time in expires_in().
77     /// </summary>
78     enum
79     {
80         undefined_expiration = -1
81     };
82 
83     oauth2_token(utility::string_t access_token = utility::string_t())
m_access_token(std::move (access_token))84         : m_access_token(std::move(access_token)), m_expires_in(undefined_expiration)
85     {
86     }
87 
88     /// <summary>
89     /// Get access token validity state.
90     /// If true, access token is a valid.
91     /// </summary>
92     /// <returns>Access token validity state.</returns>
is_valid_access_token()93     bool is_valid_access_token() const { return !access_token().empty(); }
94 
95     /// <summary>
96     /// Get access token.
97     /// </summary>
98     /// <returns>Access token string.</returns>
access_token()99     const utility::string_t& access_token() const { return m_access_token; }
100     /// <summary>
101     /// Set access token.
102     /// </summary>
103     /// <param name="access_token">Access token string to set.</param>
set_access_token(utility::string_t access_token)104     void set_access_token(utility::string_t access_token) { m_access_token = std::move(access_token); }
105 
106     /// <summary>
107     /// Get refresh token.
108     /// </summary>
109     /// <returns>Refresh token string.</returns>
refresh_token()110     const utility::string_t& refresh_token() const { return m_refresh_token; }
111     /// <summary>
112     /// Set refresh token.
113     /// </summary>
114     /// <param name="refresh_token">Refresh token string to set.</param>
set_refresh_token(utility::string_t refresh_token)115     void set_refresh_token(utility::string_t refresh_token) { m_refresh_token = std::move(refresh_token); }
116 
117     /// <summary>
118     /// Get token type.
119     /// </summary>
120     /// <returns>Token type string.</returns>
token_type()121     const utility::string_t& token_type() const { return m_token_type; }
122     /// <summary>
123     /// Set token type.
124     /// </summary>
125     /// <param name="token_type">Token type string to set.</param>
set_token_type(utility::string_t token_type)126     void set_token_type(utility::string_t token_type) { m_token_type = std::move(token_type); }
127 
128     /// <summary>
129     /// Get token scope.
130     /// </summary>
131     /// <returns>Token scope string.</returns>
scope()132     const utility::string_t& scope() const { return m_scope; }
133     /// <summary>
134     /// Set token scope.
135     /// </summary>
136     /// <param name="scope">Token scope string to set.</param>
set_scope(utility::string_t scope)137     void set_scope(utility::string_t scope) { m_scope = std::move(scope); }
138 
139     /// <summary>
140     /// Get the lifetime of the access token in seconds.
141     /// For example, 3600 means the access token will expire in one hour from
142     /// the time when access token response was generated by the authorization server.
143     /// Value of undefined_expiration means expiration time is either
144     /// unset or that it was not returned by the server with the access token.
145     /// </summary>
146     /// <returns>Lifetime of the access token in seconds or undefined_expiration if not set.</returns>
expires_in()147     int64_t expires_in() const { return m_expires_in; }
148     /// <summary>
149     /// Set lifetime of access token (in seconds).
150     /// </summary>
151     /// <param name="expires_in">Lifetime of access token in seconds.</param>
set_expires_in(int64_t expires_in)152     void set_expires_in(int64_t expires_in) { m_expires_in = expires_in; }
153 
154 private:
155     utility::string_t m_access_token;
156     utility::string_t m_refresh_token;
157     utility::string_t m_token_type;
158     utility::string_t m_scope;
159     int64_t m_expires_in;
160 };
161 
162 /// <summary>
163 /// OAuth 2.0 configuration.
164 ///
165 /// Encapsulates functionality for:
166 /// -  Authenticating requests with an access token.
167 /// -  Performing the OAuth 2.0 authorization code grant authorization flow.
168 ///    See: http://tools.ietf.org/html/rfc6749#section-4.1
169 /// -  Performing the OAuth 2.0 implicit grant authorization flow.
170 ///    See: http://tools.ietf.org/html/rfc6749#section-4.2
171 ///
172 /// Performing OAuth 2.0 authorization:
173 /// 1. Set service and client/app parameters:
174 /// -  Client/app key & secret (as provided by the service).
175 /// -  The service authorization endpoint and token endpoint.
176 /// -  Your client/app redirect URI.
177 /// -  Use set_state() to assign a unique state string for the authorization
178 ///    session (default: "").
179 /// -  If needed, use set_bearer_auth() to control bearer token passing in either
180 ///    query or header (default: header). See: http://tools.ietf.org/html/rfc6750#section-2
181 /// -  If needed, use set_access_token_key() to set "non-standard" access token
182 ///    key (default: "access_token").
183 /// -  If needed, use set_implicit_grant() to enable implicit grant flow.
184 /// 2. Build authorization URI with build_authorization_uri() and open this in web browser/control.
185 /// 3. The resource owner should then clicks "Yes" to authorize your client/app, and
186 ///    as a result the web browser/control is redirected to redirect_uri().
187 /// 5. Capture the redirected URI either in web control or by HTTP listener.
188 /// 6. Pass the redirected URI to token_from_redirected_uri() to obtain access token.
189 /// -  The method ensures redirected URI contains same state() as set in step 1.
190 /// -  In implicit_grant() is false, this will create HTTP request to fetch access token
191 ///    from the service. Otherwise access token is already included in the redirected URI.
192 ///
193 /// Usage for issuing authenticated requests:
194 /// 1. Perform authorization as above to obtain the access token or use an existing token.
195 /// -  Some services provide option to generate access tokens for testing purposes.
196 /// 2. Pass the resulting oauth2_config with the access token to http_client_config::set_oauth2().
197 /// 3. Construct http_client with this http_client_config. As a result, all HTTP requests
198 ///    by that client will be OAuth 2.0 authenticated.
199 ///
200 /// </summary>
201 class oauth2_config
202 {
203 public:
204     oauth2_config(utility::string_t client_key,
205                   utility::string_t client_secret,
206                   utility::string_t auth_endpoint,
207                   utility::string_t token_endpoint,
208                   utility::string_t redirect_uri,
209                   utility::string_t scope = utility::string_t(),
210                   utility::string_t user_agent = utility::string_t())
m_client_key(std::move (client_key))211         : m_client_key(std::move(client_key))
212         , m_client_secret(std::move(client_secret))
213         , m_auth_endpoint(std::move(auth_endpoint))
214         , m_token_endpoint(std::move(token_endpoint))
215         , m_redirect_uri(std::move(redirect_uri))
216         , m_scope(std::move(scope))
217         , m_user_agent(std::move(user_agent))
218         , m_implicit_grant(false)
219         , m_bearer_auth(true)
220         , m_http_basic_auth(true)
221         , m_access_token_key(details::oauth2_strings::access_token)
222     {
223     }
224 
225     /// <summary>
226     /// Builds an authorization URI to be loaded in the web browser/view.
227     /// The URI is built with auth_endpoint() as basis.
228     /// The implicit_grant() affects the built URI by selecting
229     /// either authorization code or implicit grant flow.
230     /// You can set generate_state to generate a new random state string.
231     /// </summary>
232     /// <param name="generate_state">If true, a new random state() string is generated
233     /// which replaces the current state(). If false, state() is unchanged and used as-is.</param>
234     /// <returns>Authorization URI string.</returns>
235     _ASYNCRTIMP utility::string_t build_authorization_uri(bool generate_state);
236 
237     /// <summary>
238     /// Fetch an access token (and possibly a refresh token) based on redirected URI.
239     /// Behavior depends on the implicit_grant() setting.
240     /// If implicit_grant() is false, the URI is parsed for 'code'
241     /// parameter, and then token_from_code() is called with this code.
242     /// See: http://tools.ietf.org/html/rfc6749#section-4.1
243     /// Otherwise, redirect URI fragment part is parsed for 'access_token'
244     /// parameter, which directly contains the token(s).
245     /// See: http://tools.ietf.org/html/rfc6749#section-4.2
246     /// In both cases, the 'state' parameter is parsed and is verified to match state().
247     /// </summary>
248     /// <param name="redirected_uri">The URI where web browser/view was redirected after resource owner's
249     /// authorization.</param> <returns>Task that fetches the token(s) based on redirected URI.</returns>
250     _ASYNCRTIMP pplx::task<void> token_from_redirected_uri(const web::http::uri& redirected_uri);
251 
252     /// <summary>
253     /// Fetches an access token (and possibly a refresh token) from the token endpoint.
254     /// The task creates an HTTP request to the token_endpoint() which exchanges
255     /// the authorization code for the token(s).
256     /// This also sets the refresh token if one was returned.
257     /// See: http://tools.ietf.org/html/rfc6749#section-4.1.3
258     /// </summary>
259     /// <param name="authorization_code">Code received via redirect upon successful authorization.</param>
260     /// <returns>Task that fetches token(s) based on the authorization code.</returns>
token_from_code(utility::string_t authorization_code)261     pplx::task<void> token_from_code(utility::string_t authorization_code)
262     {
263         uri_builder ub;
264         ub.append_query(details::oauth2_strings::grant_type, details::oauth2_strings::authorization_code, false);
265         ub.append_query(details::oauth2_strings::code, uri::encode_data_string(std::move(authorization_code)), false);
266         ub.append_query(details::oauth2_strings::redirect_uri, uri::encode_data_string(redirect_uri()), false);
267         return _request_token(ub);
268     }
269 
270     /// <summary>
271     /// Fetches a new access token (and possibly a new refresh token) using the refresh token.
272     /// The task creates a HTTP request to the token_endpoint().
273     /// If successful, resulting access token is set as active via set_token().
274     /// See: http://tools.ietf.org/html/rfc6749#section-6
275     /// This also sets a new refresh token if one was returned.
276     /// </summary>
277     /// <returns>Task that fetches the token(s) using the refresh token.</returns>
token_from_refresh()278     pplx::task<void> token_from_refresh()
279     {
280         uri_builder ub;
281         ub.append_query(details::oauth2_strings::grant_type, details::oauth2_strings::refresh_token, false);
282         ub.append_query(
283             details::oauth2_strings::refresh_token, uri::encode_data_string(token().refresh_token()), false);
284         return _request_token(ub);
285     }
286 
287     /// <summary>
288     /// Returns enabled state of the configuration.
289     /// The oauth2_handler will perform OAuth 2.0 authentication only if
290     /// this method returns true.
291     /// Return value is true if access token is valid (=fetched or manually set).
292     /// </summary>
293     /// <returns>The configuration enabled state.</returns>
is_enabled()294     bool is_enabled() const { return token().is_valid_access_token(); }
295 
296     /// <summary>
297     /// Get client key.
298     /// </summary>
299     /// <returns>Client key string.</returns>
client_key()300     const utility::string_t& client_key() const { return m_client_key; }
301     /// <summary>
302     /// Set client key.
303     /// </summary>
304     /// <param name="client_key">Client key string to set.</param>
set_client_key(utility::string_t client_key)305     void set_client_key(utility::string_t client_key) { m_client_key = std::move(client_key); }
306 
307     /// <summary>
308     /// Get client secret.
309     /// </summary>
310     /// <returns>Client secret string.</returns>
client_secret()311     const utility::string_t& client_secret() const { return m_client_secret; }
312     /// <summary>
313     /// Set client secret.
314     /// </summary>
315     /// <param name="client_secret">Client secret string to set.</param>
set_client_secret(utility::string_t client_secret)316     void set_client_secret(utility::string_t client_secret) { m_client_secret = std::move(client_secret); }
317 
318     /// <summary>
319     /// Get authorization endpoint URI string.
320     /// </summary>
321     /// <returns>Authorization endpoint URI string.</returns>
auth_endpoint()322     const utility::string_t& auth_endpoint() const { return m_auth_endpoint; }
323     /// <summary>
324     /// Set authorization endpoint URI string.
325     /// </summary>
326     /// <param name="auth_endpoint">Authorization endpoint URI string to set.</param>
set_auth_endpoint(utility::string_t auth_endpoint)327     void set_auth_endpoint(utility::string_t auth_endpoint) { m_auth_endpoint = std::move(auth_endpoint); }
328 
329     /// <summary>
330     /// Get token endpoint URI string.
331     /// </summary>
332     /// <returns>Token endpoint URI string.</returns>
token_endpoint()333     const utility::string_t& token_endpoint() const { return m_token_endpoint; }
334     /// <summary>
335     /// Set token endpoint URI string.
336     /// </summary>
337     /// <param name="token_endpoint">Token endpoint URI string to set.</param>
set_token_endpoint(utility::string_t token_endpoint)338     void set_token_endpoint(utility::string_t token_endpoint) { m_token_endpoint = std::move(token_endpoint); }
339 
340     /// <summary>
341     /// Get redirect URI string.
342     /// </summary>
343     /// <returns>Redirect URI string.</returns>
redirect_uri()344     const utility::string_t& redirect_uri() const { return m_redirect_uri; }
345     /// <summary>
346     /// Set redirect URI string.
347     /// </summary>
348     /// <param name="redirect_uri">Redirect URI string to set.</param>
set_redirect_uri(utility::string_t redirect_uri)349     void set_redirect_uri(utility::string_t redirect_uri) { m_redirect_uri = std::move(redirect_uri); }
350 
351     /// <summary>
352     /// Get scope used in authorization for token.
353     /// </summary>
354     /// <returns>Scope string used in authorization.</returns>
scope()355     const utility::string_t& scope() const { return m_scope; }
356     /// <summary>
357     /// Set scope for authorization for token.
358     /// </summary>
359     /// <param name="scope">Scope string for authorization for token.</param>
set_scope(utility::string_t scope)360     void set_scope(utility::string_t scope) { m_scope = std::move(scope); }
361 
362     /// <summary>
363     /// Get client state string used in authorization.
364     /// </summary>
365     /// <returns>Client state string used in authorization.</returns>
state()366     const utility::string_t& state() { return m_state; }
367     /// <summary>
368     /// Set client state string for authorization for token.
369     /// The state string is used in authorization for security reasons
370     /// (to uniquely identify authorization sessions).
371     /// If desired, suitably secure state string can be automatically generated
372     /// by build_authorization_uri().
373     /// A good state string consist of 30 or more random alphanumeric characters.
374     /// </summary>
375     /// <param name="state">Client authorization state string to set.</param>
set_state(utility::string_t state)376     void set_state(utility::string_t state) { m_state = std::move(state); }
377 
378     /// <summary>
379     /// Get token.
380     /// </summary>
381     /// <returns>Token.</returns>
token()382     const oauth2_token& token() const { return m_token; }
383     /// <summary>
384     /// Set token.
385     /// </summary>
386     /// <param name="token">Token to set.</param>
set_token(oauth2_token token)387     void set_token(oauth2_token token) { m_token = std::move(token); }
388 
389     /// <summary>
390     /// Get implicit grant setting for authorization.
391     /// </summary>
392     /// <returns>Implicit grant setting for authorization.</returns>
implicit_grant()393     bool implicit_grant() const { return m_implicit_grant; }
394     /// <summary>
395     /// Set implicit grant setting for authorization.
396     /// False means authorization code grant is used for authorization.
397     /// True means implicit grant is used.
398     /// Default: False.
399     /// </summary>
400     /// <param name="implicit_grant">The implicit grant setting to set.</param>
set_implicit_grant(bool implicit_grant)401     void set_implicit_grant(bool implicit_grant) { m_implicit_grant = implicit_grant; }
402 
403     /// <summary>
404     /// Get bearer token authentication setting.
405     /// </summary>
406     /// <returns>Bearer token authentication setting.</returns>
bearer_auth()407     bool bearer_auth() const { return m_bearer_auth; }
408     /// <summary>
409     /// Set bearer token authentication setting.
410     /// This must be selected based on what the service accepts.
411     /// True means access token is passed in the request header. (http://tools.ietf.org/html/rfc6750#section-2.1)
412     /// False means access token in passed in the query parameters. (http://tools.ietf.org/html/rfc6750#section-2.3)
413     /// Default: True.
414     /// </summary>
415     /// <param name="bearer_auth">The bearer token authentication setting to set.</param>
set_bearer_auth(bool bearer_auth)416     void set_bearer_auth(bool bearer_auth) { m_bearer_auth = bearer_auth; }
417 
418     /// <summary>
419     /// Get HTTP Basic authentication setting for token endpoint.
420     /// </summary>
421     /// <returns>HTTP Basic authentication setting for token endpoint.</returns>
http_basic_auth()422     bool http_basic_auth() const { return m_http_basic_auth; }
423     /// <summary>
424     /// Set HTTP Basic authentication setting for token endpoint.
425     /// This setting must be selected based on what the service accepts.
426     /// True means HTTP Basic authentication is used for the token endpoint.
427     /// False means client key & secret are passed in the HTTP request body.
428     /// Default: True.
429     /// </summary>
430     /// <param name="http_basic_auth">The HTTP Basic authentication setting to set.</param>
set_http_basic_auth(bool http_basic_auth)431     void set_http_basic_auth(bool http_basic_auth) { m_http_basic_auth = http_basic_auth; }
432 
433     /// <summary>
434     /// Get access token key.
435     /// </summary>
436     /// <returns>Access token key string.</returns>
access_token_key()437     const utility::string_t& access_token_key() const { return m_access_token_key; }
438     /// <summary>
439     /// Set access token key.
440     /// If the service requires a "non-standard" key you must set it here.
441     /// Default: "access_token".
442     /// </summary>
set_access_token_key(utility::string_t access_token_key)443     void set_access_token_key(utility::string_t access_token_key) { m_access_token_key = std::move(access_token_key); }
444 
445     /// <summary>
446     /// Get the web proxy object
447     /// </summary>
448     /// <returns>A reference to the web proxy object.</returns>
proxy()449     const web_proxy& proxy() const { return m_proxy; }
450 
451     /// <summary>
452     /// Set the web proxy object that will be used by token_from_code and token_from_refresh
453     /// </summary>
454     /// <param name="proxy">A reference to the web proxy object.</param>
set_proxy(const web_proxy & proxy)455     void set_proxy(const web_proxy& proxy) { m_proxy = proxy; }
456 
457     /// <summary>
458     /// Get user agent to be used in oauth2 flows.
459     /// </summary>
460     /// <returns>User agent string.</returns>
user_agent()461     const utility::string_t& user_agent() const { return m_user_agent; }
462     /// <summary>
463     /// Set user agent to be used in oauth2 flows.
464     /// If none is provided a default user agent is provided.
465     /// </summary>
set_user_agent(utility::string_t user_agent)466     void set_user_agent(utility::string_t user_agent) { m_user_agent = std::move(user_agent); }
467 
468 private:
469     friend class web::http::client::http_client_config;
470     friend class web::http::oauth2::details::oauth2_handler;
471 
oauth2_config()472     oauth2_config() : m_implicit_grant(false), m_bearer_auth(true), m_http_basic_auth(true) {}
473 
474     _ASYNCRTIMP pplx::task<void> _request_token(uri_builder& request_body);
475 
476     oauth2_token _parse_token_from_json(const json::value& token_json);
477 
_authenticate_request(http_request & req)478     void _authenticate_request(http_request& req) const
479     {
480         if (bearer_auth())
481         {
482             req.headers().add(header_names::authorization, _XPLATSTR("Bearer ") + token().access_token());
483         }
484         else
485         {
486             uri_builder ub(req.request_uri());
487             ub.append_query(access_token_key(), token().access_token());
488             req.set_request_uri(ub.to_uri());
489         }
490     }
491 
492     utility::string_t m_client_key;
493     utility::string_t m_client_secret;
494     utility::string_t m_auth_endpoint;
495     utility::string_t m_token_endpoint;
496     utility::string_t m_redirect_uri;
497     utility::string_t m_scope;
498     utility::string_t m_state;
499     utility::string_t m_user_agent;
500 
501     web::web_proxy m_proxy;
502 
503     bool m_implicit_grant;
504     bool m_bearer_auth;
505     bool m_http_basic_auth;
506     utility::string_t m_access_token_key;
507 
508     oauth2_token m_token;
509 
510     utility::nonce_generator m_state_generator;
511 };
512 
513 } // namespace experimental
514 
515 namespace details
516 {
517 class oauth2_handler : public http_pipeline_stage
518 {
519 public:
oauth2_handler(std::shared_ptr<experimental::oauth2_config> cfg)520     oauth2_handler(std::shared_ptr<experimental::oauth2_config> cfg) : m_config(std::move(cfg)) {}
521 
propagate(http_request request)522     virtual pplx::task<http_response> propagate(http_request request) override
523     {
524         if (m_config)
525         {
526             m_config->_authenticate_request(request);
527         }
528         return next_stage()->propagate(request);
529     }
530 
531 private:
532     std::shared_ptr<experimental::oauth2_config> m_config;
533 };
534 
535 } // namespace details
536 } // namespace oauth2
537 } // namespace http
538 } // namespace web
539 
540 #endif
541