using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Net.Http; using Newtonsoft.Json.Linq; namespace qbridge.Controllers { [Route("[controller]")] [ApiController] [Produces("application/json")] public class OAuthRedirectController : ControllerBase { //used for discovery document //https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.0 private readonly IHttpClientFactory _clientFactory; public JObject DiscoveryDoc { get; private set; } /* Discovery document https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/openid-connect#discovery-document Endpoints: https://developer.api.intuit.com/.well-known/openid_sandbox_configuration https://developer.api.intuit.com/.well-known/openid_configuration QB Online developer account login creds for sandbox: login: support@ayanova.com pw: i8BREAKfast! Development tokens for QBOI oAuth2 "AyaNova_QBOI_2" https://developer.intuit.com/v2/ui#/app/appdetail/b7urd26wgx/b7urd26xgp/keys ClientID ABj70Wv5gDauFd9KgKFwuvpQjfzTwEgodEG8tnBbS8mSQhNrZJ Client Secret XUmJyvEcEuwQuyhARUAm0a8G3gzbEAeMiATCLyFZ Sandbox: https://c50.sandbox.qbo.intuit.com/app/homepage sandbox company_us_1 sandbox-quickbooks.api.intuit.com */ public OAuthRedirectController(IHttpClientFactory clientFactory) { _clientFactory = clientFactory; } // Redirect endpoint //Step 4 here: https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/openid-connect [HttpGet] public IActionResult Get([FromQuery]string state, [FromQuery]string code) { //https://localhost:5001/oauthredirect?state=bar&code=foo return Content($"State: {state}, Code: {code}"); } [HttpGet("Start")] public async Task GetAsync() { await GetQBDiscoveryDocument(); if (DiscoveryDoc == null) { return Content($"

Error - Unable to fetch Discovery document from QuickBooks Online

Cannot proceed"); } var authorizationEndpoint = DiscoveryDoc["authorization_endpoint"].Value(); if (string.IsNullOrWhiteSpace(authorizationEndpoint)) { return Content($"

Error - Unable to find AuthorizationEndpoint value in Discovery document from QuickBooks Online

Cannot proceed"); } string url = string.Empty; var queryParams = new Dictionary() { {"client_id", "ABj70Wv5gDauFd9KgKFwuvpQjfzTwEgodEG8tnBbS8mSQhNrZJ" }, {"scope", "openid" }, {"redirect_uri","https://localhost:5001/OAuthRedirect" }, {"response_type","code"}, {"state","MyUniqueStateID"} }; url = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(authorizationEndpoint, queryParams); return Redirect(url); //will ask for login creds and permission then will redirect back to the Get method above with: //State: MyUniqueStateID, Code: AB11569366500tshFDRPEuR28l4vTjpXWuwFldE44rMng98Gn9 //which in turn is *then* used to actually get the access token //which is step 5 here: https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/openid-connect#step-5-exchange-authorization-code-to-obtain-id-token-and-access-token } public async Task GetQBDiscoveryDocument() { /* issuer:"https://oauth.platform.intuit.com/op/v1", authorization_endpoint:"https://appcenter.intuit.com/connect/oauth2", token_endpoint:"https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer", userinfo_endpoint:"https://accounts.intuit.com/v1/openid_connect/userinfo", revocation_endpoint:"https://developer.API.intuit.com/v2/oauth2/tokens/revoke", jwks_uri:"https://oauth.platform.intuit.com/op/v1/jwks", */ var request = new HttpRequestMessage(HttpMethod.Get, "https://developer.api.intuit.com/.well-known/openid_sandbox_configuration"); request.Headers.Add("Accept", "application/json"); request.Headers.Add("User-Agent", "AyaNova-QBridge"); var client = _clientFactory.CreateClient(); var response = await client.SendAsync(request); if (response.IsSuccessStatusCode) { string data = await response.Content.ReadAsStringAsync(); DiscoveryDoc = JObject.Parse(data); } else { DiscoveryDoc = null; } return; // string baseUrl = "https://developer.api.intuit.com/.well-known/openid_sandbox_configuration"; //The 'using' will help to prevent memory leaks. //Create a new instance of HttpClient // using (System.Net.Http.HttpClient client = new HttpClient()) // //Setting up the response... // using (HttpResponseMessage res = await client.GetAsync(baseUrl)) // using (HttpContent content = res.Content) // { // string data = await content.ReadAsStringAsync(); // if (data != null) // { // Console.WriteLine(data); // } // } } /* Plan: Make a web APP and api that runs on our server and handles getting tokens from the QB Online oAuth2 endpoints Docs for normal development are here: https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization Tentative process: Borrowing from the technique and concepts outlined here: http://relasoft.net/KB10004.html and here: https://github.com/IntuitDeveloper/C2QB-library-for-Windows-CUI-and-GUI/issues/1#issuecomment-511172847 User runs QBOI plugin, if it needs a new access token then it shells out to browser (with random temp session ID number to uniquely identify this user) to go to *our* qBridge auth page. User enters their creds to login to QBOnline instance. QBridge passes creds (along with random session id as the extra parameter they allow) on to the QBOI auth page which when successful redirects browser to the QBridge page we've specified as the "redirect url" with the tokens in the url and also our unique session ID which then shows the end user that it's success and stores the tokens somewhere (gonna need a db I guess) for fetching by QBOI. Meanwhile, in the background, QBOI is polling a route on qbridge with the unique ID number looking for a return of the tokens it needs to proceed. Once it fetches the "Access token" and "Refresh token" it needs successfully then it continues on to normal usage If it gets a response that the token needs to be refreshed, it either hands this operation off to qBridge or does it itself (not sure at this point which way it's supposed to happen) If the access token expires after 100 days or so then they repeat this process (automatically by QBOI) */ // POST: api/Todo // [HttpPost] // public async Task> Post(QCreds creds) // { // var q = new QItem(); // q.Token1 = "Test token 1"; // q.Token2 = System.DateTime.Now.ToString(); // q.Token3 = creds.Login; // return Ok(q); // //return CreatedAtAction(nameof(GetTodoItem), new { id = item.Id }, item); // } // public class QCreds // { // public string Login { get; set; } // public string Password { get; set; } // } // ************************************************************************************************************* } }