/*** * Copyright (C) Microsoft. All rights reserved. * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. * * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * * HTTP Library: Oauth 2.0 * * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk * * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- ****/ #pragma once #ifndef CASA_OAUTH2_H #define CASA_OAUTH2_H #include "cpprest/details/web_utilities.h" #include "cpprest/http_msg.h" namespace web { namespace http { namespace client { // Forward declaration to avoid circular include dependency. class http_client_config; } // namespace client /// oAuth 2.0 library. namespace oauth2 { namespace details { class oauth2_handler; // Constant strings for OAuth 2.0. typedef utility::string_t oauth2_string; class oauth2_strings { public: #define _OAUTH2_STRINGS #define DAT(a_, b_) _ASYNCRTIMP static const oauth2_string a_; #include "cpprest/details/http_constants.dat" #undef _OAUTH2_STRINGS #undef DAT }; } // namespace details /// oAuth functionality is currently in beta. namespace experimental { /// /// Exception type for OAuth 2.0 errors. /// class oauth2_exception : public std::exception { public: oauth2_exception(utility::string_t msg) : m_msg(utility::conversions::to_utf8string(std::move(msg))) {} ~oauth2_exception() CPPREST_NOEXCEPT {} const char* what() const CPPREST_NOEXCEPT { return m_msg.c_str(); } private: std::string m_msg; }; /// /// OAuth 2.0 token and associated information. /// class oauth2_token { public: /// /// Value for undefined expiration time in expires_in(). /// enum { undefined_expiration = -1 }; oauth2_token(utility::string_t access_token = utility::string_t()) : m_access_token(std::move(access_token)), m_expires_in(undefined_expiration) { } /// /// Get access token validity state. /// If true, access token is a valid. /// /// Access token validity state. bool is_valid_access_token() const { return !access_token().empty(); } /// /// Get access token. /// /// Access token string. const utility::string_t& access_token() const { return m_access_token; } /// /// Set access token. /// /// Access token string to set. void set_access_token(utility::string_t access_token) { m_access_token = std::move(access_token); } /// /// Get refresh token. /// /// Refresh token string. const utility::string_t& refresh_token() const { return m_refresh_token; } /// /// Set refresh token. /// /// Refresh token string to set. void set_refresh_token(utility::string_t refresh_token) { m_refresh_token = std::move(refresh_token); } /// /// Get token type. /// /// Token type string. const utility::string_t& token_type() const { return m_token_type; } /// /// Set token type. /// /// Token type string to set. void set_token_type(utility::string_t token_type) { m_token_type = std::move(token_type); } /// /// Get token scope. /// /// Token scope string. const utility::string_t& scope() const { return m_scope; } /// /// Set token scope. /// /// Token scope string to set. void set_scope(utility::string_t scope) { m_scope = std::move(scope); } /// /// Get the lifetime of the access token in seconds. /// For example, 3600 means the access token will expire in one hour from /// the time when access token response was generated by the authorization server. /// Value of undefined_expiration means expiration time is either /// unset or that it was not returned by the server with the access token. /// /// Lifetime of the access token in seconds or undefined_expiration if not set. int64_t expires_in() const { return m_expires_in; } /// /// Set lifetime of access token (in seconds). /// /// Lifetime of access token in seconds. void set_expires_in(int64_t expires_in) { m_expires_in = expires_in; } private: utility::string_t m_access_token; utility::string_t m_refresh_token; utility::string_t m_token_type; utility::string_t m_scope; int64_t m_expires_in; }; /// /// OAuth 2.0 configuration. /// /// Encapsulates functionality for: /// - Authenticating requests with an access token. /// - Performing the OAuth 2.0 authorization code grant authorization flow. /// See: http://tools.ietf.org/html/rfc6749#section-4.1 /// - Performing the OAuth 2.0 implicit grant authorization flow. /// See: http://tools.ietf.org/html/rfc6749#section-4.2 /// /// Performing OAuth 2.0 authorization: /// 1. Set service and client/app parameters: /// - Client/app key & secret (as provided by the service). /// - The service authorization endpoint and token endpoint. /// - Your client/app redirect URI. /// - Use set_state() to assign a unique state string for the authorization /// session (default: ""). /// - If needed, use set_bearer_auth() to control bearer token passing in either /// query or header (default: header). See: http://tools.ietf.org/html/rfc6750#section-2 /// - If needed, use set_access_token_key() to set "non-standard" access token /// key (default: "access_token"). /// - If needed, use set_implicit_grant() to enable implicit grant flow. /// 2. Build authorization URI with build_authorization_uri() and open this in web browser/control. /// 3. The resource owner should then clicks "Yes" to authorize your client/app, and /// as a result the web browser/control is redirected to redirect_uri(). /// 5. Capture the redirected URI either in web control or by HTTP listener. /// 6. Pass the redirected URI to token_from_redirected_uri() to obtain access token. /// - The method ensures redirected URI contains same state() as set in step 1. /// - In implicit_grant() is false, this will create HTTP request to fetch access token /// from the service. Otherwise access token is already included in the redirected URI. /// /// Usage for issuing authenticated requests: /// 1. Perform authorization as above to obtain the access token or use an existing token. /// - Some services provide option to generate access tokens for testing purposes. /// 2. Pass the resulting oauth2_config with the access token to http_client_config::set_oauth2(). /// 3. Construct http_client with this http_client_config. As a result, all HTTP requests /// by that client will be OAuth 2.0 authenticated. /// /// class oauth2_config { public: oauth2_config(utility::string_t client_key, utility::string_t client_secret, utility::string_t auth_endpoint, utility::string_t token_endpoint, utility::string_t redirect_uri, utility::string_t scope = utility::string_t(), utility::string_t user_agent = utility::string_t()) : m_client_key(std::move(client_key)) , m_client_secret(std::move(client_secret)) , m_auth_endpoint(std::move(auth_endpoint)) , m_token_endpoint(std::move(token_endpoint)) , m_redirect_uri(std::move(redirect_uri)) , m_scope(std::move(scope)) , m_user_agent(std::move(user_agent)) , m_implicit_grant(false) , m_bearer_auth(true) , m_http_basic_auth(true) , m_access_token_key(details::oauth2_strings::access_token) { } /// /// Builds an authorization URI to be loaded in the web browser/view. /// The URI is built with auth_endpoint() as basis. /// The implicit_grant() affects the built URI by selecting /// either authorization code or implicit grant flow. /// You can set generate_state to generate a new random state string. /// /// If true, a new random state() string is generated /// which replaces the current state(). If false, state() is unchanged and used as-is. /// Authorization URI string. _ASYNCRTIMP utility::string_t build_authorization_uri(bool generate_state); /// /// Fetch an access token (and possibly a refresh token) based on redirected URI. /// Behavior depends on the implicit_grant() setting. /// If implicit_grant() is false, the URI is parsed for 'code' /// parameter, and then token_from_code() is called with this code. /// See: http://tools.ietf.org/html/rfc6749#section-4.1 /// Otherwise, redirect URI fragment part is parsed for 'access_token' /// parameter, which directly contains the token(s). /// See: http://tools.ietf.org/html/rfc6749#section-4.2 /// In both cases, the 'state' parameter is parsed and is verified to match state(). /// /// The URI where web browser/view was redirected after resource owner's /// authorization. Task that fetches the token(s) based on redirected URI. _ASYNCRTIMP pplx::task token_from_redirected_uri(const web::http::uri& redirected_uri); /// /// Fetches an access token (and possibly a refresh token) from the token endpoint. /// The task creates an HTTP request to the token_endpoint() which exchanges /// the authorization code for the token(s). /// This also sets the refresh token if one was returned. /// See: http://tools.ietf.org/html/rfc6749#section-4.1.3 /// /// Code received via redirect upon successful authorization. /// Task that fetches token(s) based on the authorization code. pplx::task token_from_code(utility::string_t authorization_code) { uri_builder ub; ub.append_query(details::oauth2_strings::grant_type, details::oauth2_strings::authorization_code, false); ub.append_query(details::oauth2_strings::code, uri::encode_data_string(std::move(authorization_code)), false); ub.append_query(details::oauth2_strings::redirect_uri, uri::encode_data_string(redirect_uri()), false); return _request_token(ub); } /// /// Fetches a new access token (and possibly a new refresh token) using the refresh token. /// The task creates a HTTP request to the token_endpoint(). /// If successful, resulting access token is set as active via set_token(). /// See: http://tools.ietf.org/html/rfc6749#section-6 /// This also sets a new refresh token if one was returned. /// /// Task that fetches the token(s) using the refresh token. pplx::task token_from_refresh() { uri_builder ub; ub.append_query(details::oauth2_strings::grant_type, details::oauth2_strings::refresh_token, false); ub.append_query( details::oauth2_strings::refresh_token, uri::encode_data_string(token().refresh_token()), false); return _request_token(ub); } /// /// Returns enabled state of the configuration. /// The oauth2_handler will perform OAuth 2.0 authentication only if /// this method returns true. /// Return value is true if access token is valid (=fetched or manually set). /// /// The configuration enabled state. bool is_enabled() const { return token().is_valid_access_token(); } /// /// Get client key. /// /// Client key string. const utility::string_t& client_key() const { return m_client_key; } /// /// Set client key. /// /// Client key string to set. void set_client_key(utility::string_t client_key) { m_client_key = std::move(client_key); } /// /// Get client secret. /// /// Client secret string. const utility::string_t& client_secret() const { return m_client_secret; } /// /// Set client secret. /// /// Client secret string to set. void set_client_secret(utility::string_t client_secret) { m_client_secret = std::move(client_secret); } /// /// Get authorization endpoint URI string. /// /// Authorization endpoint URI string. const utility::string_t& auth_endpoint() const { return m_auth_endpoint; } /// /// Set authorization endpoint URI string. /// /// Authorization endpoint URI string to set. void set_auth_endpoint(utility::string_t auth_endpoint) { m_auth_endpoint = std::move(auth_endpoint); } /// /// Get token endpoint URI string. /// /// Token endpoint URI string. const utility::string_t& token_endpoint() const { return m_token_endpoint; } /// /// Set token endpoint URI string. /// /// Token endpoint URI string to set. void set_token_endpoint(utility::string_t token_endpoint) { m_token_endpoint = std::move(token_endpoint); } /// /// Get redirect URI string. /// /// Redirect URI string. const utility::string_t& redirect_uri() const { return m_redirect_uri; } /// /// Set redirect URI string. /// /// Redirect URI string to set. void set_redirect_uri(utility::string_t redirect_uri) { m_redirect_uri = std::move(redirect_uri); } /// /// Get scope used in authorization for token. /// /// Scope string used in authorization. const utility::string_t& scope() const { return m_scope; } /// /// Set scope for authorization for token. /// /// Scope string for authorization for token. void set_scope(utility::string_t scope) { m_scope = std::move(scope); } /// /// Get client state string used in authorization. /// /// Client state string used in authorization. const utility::string_t& state() { return m_state; } /// /// Set client state string for authorization for token. /// The state string is used in authorization for security reasons /// (to uniquely identify authorization sessions). /// If desired, suitably secure state string can be automatically generated /// by build_authorization_uri(). /// A good state string consist of 30 or more random alphanumeric characters. /// /// Client authorization state string to set. void set_state(utility::string_t state) { m_state = std::move(state); } /// /// Get token. /// /// Token. const oauth2_token& token() const { return m_token; } /// /// Set token. /// /// Token to set. void set_token(oauth2_token token) { m_token = std::move(token); } /// /// Get implicit grant setting for authorization. /// /// Implicit grant setting for authorization. bool implicit_grant() const { return m_implicit_grant; } /// /// Set implicit grant setting for authorization. /// False means authorization code grant is used for authorization. /// True means implicit grant is used. /// Default: False. /// /// The implicit grant setting to set. void set_implicit_grant(bool implicit_grant) { m_implicit_grant = implicit_grant; } /// /// Get bearer token authentication setting. /// /// Bearer token authentication setting. bool bearer_auth() const { return m_bearer_auth; } /// /// Set bearer token authentication setting. /// This must be selected based on what the service accepts. /// True means access token is passed in the request header. (http://tools.ietf.org/html/rfc6750#section-2.1) /// False means access token in passed in the query parameters. (http://tools.ietf.org/html/rfc6750#section-2.3) /// Default: True. /// /// The bearer token authentication setting to set. void set_bearer_auth(bool bearer_auth) { m_bearer_auth = bearer_auth; } /// /// Get HTTP Basic authentication setting for token endpoint. /// /// HTTP Basic authentication setting for token endpoint. bool http_basic_auth() const { return m_http_basic_auth; } /// /// Set HTTP Basic authentication setting for token endpoint. /// This setting must be selected based on what the service accepts. /// True means HTTP Basic authentication is used for the token endpoint. /// False means client key & secret are passed in the HTTP request body. /// Default: True. /// /// The HTTP Basic authentication setting to set. void set_http_basic_auth(bool http_basic_auth) { m_http_basic_auth = http_basic_auth; } /// /// Get access token key. /// /// Access token key string. const utility::string_t& access_token_key() const { return m_access_token_key; } /// /// Set access token key. /// If the service requires a "non-standard" key you must set it here. /// Default: "access_token". /// void set_access_token_key(utility::string_t access_token_key) { m_access_token_key = std::move(access_token_key); } /// /// Get the web proxy object /// /// A reference to the web proxy object. const web_proxy& proxy() const { return m_proxy; } /// /// Set the web proxy object that will be used by token_from_code and token_from_refresh /// /// A reference to the web proxy object. void set_proxy(const web_proxy& proxy) { m_proxy = proxy; } /// /// Get user agent to be used in oauth2 flows. /// /// User agent string. const utility::string_t& user_agent() const { return m_user_agent; } /// /// Set user agent to be used in oauth2 flows. /// If none is provided a default user agent is provided. /// void set_user_agent(utility::string_t user_agent) { m_user_agent = std::move(user_agent); } private: friend class web::http::client::http_client_config; friend class web::http::oauth2::details::oauth2_handler; oauth2_config() : m_implicit_grant(false), m_bearer_auth(true), m_http_basic_auth(true) {} _ASYNCRTIMP pplx::task _request_token(uri_builder& request_body); oauth2_token _parse_token_from_json(const json::value& token_json); void _authenticate_request(http_request& req) const { if (bearer_auth()) { req.headers().add(header_names::authorization, _XPLATSTR("Bearer ") + token().access_token()); } else { uri_builder ub(req.request_uri()); ub.append_query(access_token_key(), token().access_token()); req.set_request_uri(ub.to_uri()); } } utility::string_t m_client_key; utility::string_t m_client_secret; utility::string_t m_auth_endpoint; utility::string_t m_token_endpoint; utility::string_t m_redirect_uri; utility::string_t m_scope; utility::string_t m_state; utility::string_t m_user_agent; web::web_proxy m_proxy; bool m_implicit_grant; bool m_bearer_auth; bool m_http_basic_auth; utility::string_t m_access_token_key; oauth2_token m_token; utility::nonce_generator m_state_generator; }; } // namespace experimental namespace details { class oauth2_handler : public http_pipeline_stage { public: oauth2_handler(std::shared_ptr cfg) : m_config(std::move(cfg)) {} virtual pplx::task propagate(http_request request) override { if (m_config) { m_config->_authenticate_request(request); } return next_stage()->propagate(request); } private: std::shared_ptr m_config; }; } // namespace details } // namespace oauth2 } // namespace http } // namespace web #endif