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