【问题标题】:QBO API Connect and Authorize in the same stepQBO API 连接和授权在同一步骤中
【发布时间】:2018-12-25 20:02:06
【问题描述】:

我正在使用 QBO API SDK (IppDotNetSdkQuickBooksApiV3) 并且无法弄清楚如何允许用户连接到 QuickBooks 并授权我的应用程序。目前分两 (2) 步进行:

  1. 用户单击“连接到 Intuit”并被发送到 Intuit 进行登录
  2. 他们被重定向回我的应用程序,然后必须再次连接他们的实际文件

我显然错过了一些东西,但不知道它是什么。我正在使用应用程序中内置的 ipp:connecttointuit 功能,所以我不知道如何针对我正在寻找的结果对其进行自定义。

我的应用可以通过上述两个步骤运行,但是我无法使用上面详述的流程将我的应用列在 apps.com 网站中。他们(apps.com)希望用户使用他们的 QBO 凭据登录,授权应用程序,然后在应用程序正常工作的情况下自动将用户重定向回我的网站。他们不想要重复的授权(我不能怪他们)。

完全卡住了。我是一名优秀的程序员,但对 OpenId 或 OAuth 没有经验。


protected void Page_Load(object sender, EventArgs e)
        {
            var openIdRelyingParty = new OpenIdRelyingParty();
            var openid_identifier = ConfigurationManager.AppSettings["openid_identifier"];
            var returnUrl = "~/OpenID/Connect";
            var response = openIdRelyingParty.GetResponse();
            if (response == null)
            {
                // Stage 2: user submitting Identifier
                Identifier id;
                if (Identifier.TryParse(openid_identifier, out id))
                {
                    IAuthenticationRequest request = openIdRelyingParty.CreateRequest(openid_identifier);
                    FetchRequest fetch = new FetchRequest();
                    fetch.Attributes.Add(new AttributeRequest(WellKnownAttributes.Contact.Email));
                    fetch.Attributes.Add(new AttributeRequest(WellKnownAttributes.Name.FullName));
                    fetch.Attributes.Add(new AttributeRequest("http://axschema.org/intuit/realmId"));
                    request.AddExtension(fetch);
                    request.RedirectToProvider();
                }
            }
            else
            {
                if (response.FriendlyIdentifierForDisplay == null)
                {
                    Response.Redirect("~/OpenID/Connect");
                }

                // Stage 3: OpenID Provider sending assertion response
                //Session["FriendlyIdentifier"] = response.FriendlyIdentifierForDisplay;
                FetchResponse fetch = response.GetExtension<FetchResponse>();
                if (fetch != null)
                {
                    var openIdEmail = fetch.GetAttributeValue(WellKnownAttributes.Contact.Email);
                    var openIdFullName = fetch.GetAttributeValue(WellKnownAttributes.Name.FullName);
                    var openIdRealmId = fetch.GetAttributeValue("http://axschema.org/intuit/realmId");

                    string userName = Membership.GetUserNameByEmail(openIdEmail);

                    //If username is null---------------------------------------------------
                    if (userName == null)
                    {

                        //DG added this---------------------------
                        String NewPassword = Membership.GeneratePassword(6, 1);
                        Membership.CreateUser(openIdEmail, NewPassword, openIdEmail);
                        //DG added this----------------------------

                        //Membership.CreateUser(openIdEmail, Guid.NewGuid().ToString(), openIdEmail);

                        FormsAuthentication.SetAuthCookie(openIdEmail, true);
                        //if (Request.QueryString["Subscribe"] != null)
                        //{
                        String csname = "DirectConnectScript";
                        Type cstype = this.GetType();
                        ClientScriptManager csm = Page.ClientScript;

                        // Check to see if the startup script is already registered.
                        if (!csm.IsStartupScriptRegistered(cstype, csname))
                        {
                            StringBuilder cstext = new StringBuilder();
                            cstext.AppendLine("<script>");
                            cstext.AppendLine("$(document).ready(function () {");
                            cstext.AppendLine("intuit.ipp.anywhere.directConnectToIntuit();");
                            cstext.AppendLine("});");
                            cstext.AppendLine("</script>");
                            csm.RegisterStartupScript(cstype, csname, cstext.ToString());
                            //}
                        }

                    }
                    else if (Request.QueryString["Disconnect"] != null)
                    {
                        RestHelper.clearProfile(RestProfile.GetRestProfile());
                        Response.Redirect("~/ManageConnection");
                    }

                    //If username is not null---------------------------------------------------
                    else if (userName != null)
                    {
                        FormsAuthentication.SetAuthCookie(userName, true);

                        if (!string.IsNullOrEmpty(returnUrl))
                        {
                            Response.Redirect("~/ManageConnection");
                        }
                    }
                }

            }
        }

【问题讨论】:

  • 但是您没有发布任何代码,所以我们无法帮助您。
  • 感谢 Keith - 在用户单击“连接到 Intuit”按钮后,我已添加 QBO 正在点击的页面中的代码。我不确定 OAuth 部分在哪里/如何适合这里。我有单独的页面用于 OAuth 流程的调用和授予部分,但不明白它们适合的位置。
  • 您解决了吗?这里有一个步骤,您没有为其添加代码,即 OAuthResponse。我想知道您是否只是从 QB 重定向回上面的代码
  • 我还没解决:(

标签: asp.net oauth openid quickbooks-online


【解决方案1】:

我对你有感觉,我花了一段时间才自己完成这项工作。

这是我的 MVC 版本。希望对你有帮助。

它从 QuickBooks/Index 开始。使用 QB 弹出来获取我的应用程序的权限,然后从那里开始。作为奖励,如果用户已经登录到 QB,如果他们过去获得过许可,他们会自动登录到应用程序。这是因为我将加密的令牌保存在数据库中。 (忽略大部分 Session 内容,我只是从未从创建代码的示例中删除它)。

不管怎样,这里有很多代码要发布。 如果您需要任何澄清,请随时在 cmets 中提问

我假设您有类似的东西接收 OAuthResponse 并重定向回 /QuickBooks/Index(在您的情况下是您在问题中发布的页面)

public class OauthResponseController : Controller
{
    /// <summary>
    /// OAuthVerifyer, RealmId, DataSource
    /// </summary>
    private String _oauthVerifyer, _realmid, _dataSource;

    /// <summary>
    /// Action Results for Index, OAuthToken, OAuthVerifyer and RealmID is recieved as part of Response
    /// and are stored inside Session object for future references
    /// NOTE: Session storage is only used for demonstration purpose only.
    /// </summary>
    /// <returns>View Result.</returns>
    public ViewResult Index()
    {
        if (Request.QueryString.HasKeys())
        {
            // This value is used to Get Access Token.
            _oauthVerifyer = Request.QueryString.GetValues("oauth_verifier").FirstOrDefault().ToString();
            if (_oauthVerifyer.Length == 1)
            {
                _oauthVerifyer = Request.QueryString["oauth_verifier"].ToString();
            }
            _realmid = Request.QueryString.GetValues("realmId").FirstOrDefault().ToString();
            if (_realmid.Length == 1)
            {
                _realmid = Request.QueryString["realmId"].ToString();
            }
            Session["Realm"] = _realmid;

            //If dataSource is QBO call QuickBooks Online Services, else call QuickBooks Desktop Services
            _dataSource = Request.QueryString.GetValues("dataSource").FirstOrDefault().ToString();
            if (_dataSource.Length == 1)
            {
                _dataSource = Request.QueryString["dataSource"].ToString();
            }
            Session["DataSource"] = _dataSource;

            getAccessToken();


            //Production applications should securely store the Access Token.
            //In this template, encrypted Oauth access token is persisted in OauthAccessTokenStorage.xml
            OauthAccessTokenStorageHelper.StoreOauthAccessToken();

            // This value is used to redirect to Default.aspx from Cleanup page when user clicks on ConnectToInuit widget.
            Session["RedirectToDefault"] = true;
        }
        else
        {
            Response.Write("No oauth token was received");
        }

        return View(); // This will redirect to /QuickBooks/OpenIdIndex which is almost the same as the code that you have posted
    }

    /// <summary>
    /// Gets the OAuth Token
    /// </summary>
    private void getAccessToken()
    {
        IOAuthSession clientSession = CreateSession();
        try
        {
            IToken accessToken = clientSession.ExchangeRequestTokenForAccessToken((IToken)Session["requestToken"], _oauthVerifyer);
            Session["AccessToken"] = accessToken.Token;

            // Add flag to session which tells that accessToken is in session
            Session["Flag"] = true;

            // Remove the Invalid Access token since we got the new access token
            Session.Remove("InvalidAccessToken");
            Session["AccessTokenSecret"] = accessToken.TokenSecret;
        }
        catch (Exception ex)
        {
            //Handle Exception if token is rejected or exchange of Request Token for Access Token failed.
            throw ex;
        }

    }

    /// <summary>
    /// Creates User Session
    /// </summary>
    /// <returns>OAuth Session.</returns>
    private IOAuthSession CreateSession()
    {
        OAuthConsumerContext consumerContext = new OAuthConsumerContext
        {
            ConsumerKey = ConfigurationManager.AppSettings["consumerKey"].ToString(),
            ConsumerSecret = ConfigurationManager.AppSettings["consumerSecret"].ToString(),
            SignatureMethod = SignatureMethod.HmacSha1
        };

        return new OAuthSession(consumerContext,
                                        Constants.OauthEndPoints.IdFedOAuthBaseUrl + Constants.OauthEndPoints.UrlRequestToken,
                                        Constants.OauthEndPoints.IdFedOAuthBaseUrl,
                                         Constants.OauthEndPoints.IdFedOAuthBaseUrl + Constants.OauthEndPoints.UrlAccessToken);
    }
}

这是我与您的等效页面。

public class QuickBooksController : Controller
{
    private readonly IQueryChannel _queryChannel;
    private readonly ICommandChannel _commandChannel;
    public QuickBooksController(IQueryChannel queryChannel, ICommandChannel commandChannel)
    {
        _queryChannel = queryChannel;
        _commandChannel = commandChannel;
    }
    /// <summary>
    /// OpenId Relying Party
    /// </summary>
    private static OpenIdRelyingParty openid = new OpenIdRelyingParty();

    public ActionResult Index(string returnurl)
    {
        QuickBooksAuthStore.Load();
        if (QuickBooksContext.AccessToken != null)
        {
            if (!new Customers().CheckConnection())
            {
                QuickBooksContext.FriendlyName = null;
                OauthAccessTokenStorageHelper.RemoveInvalidOauthAccessToken(QuickBooksContext.FriendlyEmail);
                QuickBooksContext.AccessToken = null;
            }
        }
        if (returnurl != null || QuickBooksContext.QuickReturnUrl != null)
        {
            if (returnurl != null)
            {
               QuickBooksContext.QuickReturnUrl = returnurl; 
            }
            if (QuickBooksContext.AccessToken != null)
            {
                var connected = new Customers().CheckConnection();
                if (connected)
                {
                    returnurl = QuickBooksContext.QuickReturnUrl;
                    QuickBooksContext.QuickReturnUrl = null;
                    return Redirect(returnurl);
                }
            }  
        }
        return View();
    }

    public RedirectResult OpenIdIndex()
    {
        var openid_identifier = ConfigurationManager.AppSettings["openid_identifier"] + SessionContext.CurrentUser.MasterCompanyId;
        var response = openid.GetResponse();
        if (response == null)
        {
            // Stage 2: user submitting Identifier
            Identifier id;
            if (Identifier.TryParse(openid_identifier, out id))
            {
                try
                {
                    IAuthenticationRequest request = openid.CreateRequest(openid_identifier);
                    FetchRequest fetch = new FetchRequest();
                    fetch.Attributes.Add(new AttributeRequest(WellKnownAttributes.Contact.Email));
                    fetch.Attributes.Add(new AttributeRequest(WellKnownAttributes.Name.FullName));
                    request.AddExtension(fetch);
                    request.RedirectToProvider();
                }
                catch (ProtocolException ex)
                {
                    throw ex;
                }
            }
        }
        else
        {
            if (response.FriendlyIdentifierForDisplay == null)
            {
                Response.Redirect("/OpenId");
            }

            // Stage 3: OpenID Provider sending assertion response, storing the response in Session object is only for demonstration purpose
            Session["FriendlyIdentifier"] = response.FriendlyIdentifierForDisplay;
            FetchResponse fetch = response.GetExtension<FetchResponse>();
            if (fetch != null)
            {
                Session["OpenIdResponse"] = "True";
                Session["FriendlyEmail"] = fetch.GetAttributeValue(WellKnownAttributes.Contact.Email);// emailAddresses.Count > 0 ? emailAddresses[0] : null;
                Session["FriendlyName"] = fetch.GetAttributeValue(WellKnownAttributes.Name.FullName);//fullNames.Count > 0 ? fullNames[0] : null;

                OauthAccessTokenStorageHelper.GetOauthAccessTokenForUser(Session["FriendlyEmail"].ToString());
                QuickBooksAuthStore.UpdateFriendlyId(Session["FriendlyIdentifier"].ToString(), Session["FriendlyName"].ToString());
            }
        }

        string query = Request.Url.Query;
        if (!string.IsNullOrWhiteSpace(query) && query.ToLower().Contains("disconnect=true"))
        {
            Session["AccessToken"] = "dummyAccessToken";
            Session["AccessTokenSecret"] = "dummyAccessTokenSecret";
            Session["Flag"] = true;
            return Redirect("QuickBooks/CleanupOnDisconnect");
        }
        return Redirect("/QuickBooks/Index");
    }

    /// <summary>
    /// Service response.
    /// </summary>
    private String txtServiceResponse = "";

    /// <summary>
    /// Disconnect Flag.
    /// </summary>
    protected String DisconnectFlg = "";
    public ActionResult Disconnect()
    {
        OAuthConsumerContext consumerContext = new OAuthConsumerContext
        {
            ConsumerKey = ConfigurationManager.AppSettings["consumerKey"].ToString(),
            SignatureMethod = SignatureMethod.HmacSha1,
            ConsumerSecret = ConfigurationManager.AppSettings["consumerSecret"].ToString()
        };

        OAuthSession oSession = new OAuthSession(consumerContext, Constants.OauthEndPoints.IdFedOAuthBaseUrl + Constants.OauthEndPoints.UrlRequestToken,
                              Constants.OauthEndPoints.AuthorizeUrl,
                              Constants.OauthEndPoints.IdFedOAuthBaseUrl + Constants.OauthEndPoints.UrlAccessToken);

        oSession.ConsumerContext.UseHeaderForOAuthParameters = true;
        if ((Session["AccessToken"] + "").Length > 0)
        {
            Session["FriendlyName"] = null;
            oSession.AccessToken = new TokenBase
            {
                Token = Session["AccessToken"].ToString(),
                ConsumerKey = ConfigurationManager.AppSettings["consumerKey"].ToString(),
                TokenSecret = Session["AccessTokenSecret"].ToString()
            };

            IConsumerRequest conReq = oSession.Request();
            conReq = conReq.Get();
            conReq = conReq.ForUrl(Constants.IaEndPoints.DisconnectUrl);
            try
            {
                conReq = conReq.SignWithToken();
            }
            catch (Exception ex)
            {
                throw ex;
            }

            //Used just see the what header contains
            string header = conReq.Context.GenerateOAuthParametersForHeader();

            //This method will clean up the OAuth Token
            txtServiceResponse = conReq.ReadBody();

            //Reset All the Session Variables
            Session.Remove("oauthToken");

            // Dont remove the access token since this is required for Reconnect btn in the Blue dot menu
            // Session.Remove("accessToken");

            // Add the invalid access token into session for the display of the Disconnect btn
            Session["InvalidAccessToken"] = Session["AccessToken"];

            // Dont Remove flag since we need to display the blue dot menu for Reconnect btn in the Blue dot menu
            // Session.Remove("Flag");

            ViewBag.DisconnectFlg = "User is Disconnected from QuickBooks!";

            //Remove the Oauth access token from the OauthAccessTokenStorage.xml
            OauthAccessTokenStorageHelper.RemoveInvalidOauthAccessToken(Session["FriendlyEmail"].ToString());
        }

        return RedirectToAction("Index", "QuickBooks");
    }

    public ActionResult CleanUpOnDisconnect()
    {
        //perform the clean up here 

        // Redirect to Home when user clicks on ConenctToIntuit widget.
        object value = Session["RedirectToDefault"];
        if (value != null)
        {
            bool isTrue = (bool)value;
            if (isTrue)
            {
                Session.Remove("InvalidAccessToken");
                Session.Remove("RedirectToDefault");
                return Redirect("/QuickBooks/index");
            }
        }

        return RedirectToAction("Index", "QuickBooks");
    }

    private String consumerSecret, consumerKey, oauthLink, RequestToken, TokenSecret, oauth_callback_url;

    public RedirectResult OAuthGrant()
    {
        oauth_callback_url = Request.Url.GetLeftPart(UriPartial.Authority) + ConfigurationManager.AppSettings["oauth_callback_url"];
        consumerKey = ConfigurationManager.AppSettings["consumerKey"];
        consumerSecret = ConfigurationManager.AppSettings["consumerSecret"];
        oauthLink = Constants.OauthEndPoints.IdFedOAuthBaseUrl;
        IToken token = (IToken)Session["requestToken"];
        IOAuthSession session = CreateSession();
        IToken requestToken = session.GetRequestToken();
        Session["requestToken"] = requestToken;
        RequestToken = requestToken.Token;
        TokenSecret = requestToken.TokenSecret;

        oauthLink = Constants.OauthEndPoints.AuthorizeUrl + "?oauth_token=" + RequestToken + "&oauth_callback=" + UriUtility.UrlEncode(oauth_callback_url);
        return Redirect(oauthLink);
    }

    /// <summary>
    /// Gets the Access Token
    /// </summary>
    /// <returns>Returns OAuth Session</returns>
    protected IOAuthSession CreateSession()
    {
        OAuthConsumerContext consumerContext = new OAuthConsumerContext
        {
            ConsumerKey = consumerKey,
            ConsumerSecret = consumerSecret,
            SignatureMethod = SignatureMethod.HmacSha1
        };

        return new OAuthSession(consumerContext,
                                        Constants.OauthEndPoints.IdFedOAuthBaseUrl + Constants.OauthEndPoints.UrlRequestToken,
                                        oauthLink,
                                        Constants.OauthEndPoints.IdFedOAuthBaseUrl + Constants.OauthEndPoints.UrlAccessToken);
    }
}

..这里是 QuickBooks/Index 的 cshtml 页面

@using System.Configuration
@using PubManager.Domain.WebSession
@{
    string MenuProxy = Request.Url.GetLeftPart(UriPartial.Authority) + "/" + System.Configuration.ConfigurationManager.AppSettings["menuProxy"];
    string OauthGrant = Request.Url.GetLeftPart(UriPartial.Authority) + "/" + System.Configuration.ConfigurationManager.AppSettings["grantUrl"];

    ViewBag.Title = "MagManager - Intuit Anywhere";
    string FriendlyName = (string)HttpContext.Current.Session["FriendlyName"] + "";
    string FriendlyEmail = (string)HttpContext.Current.Session["FriendlyEmail"];
    string FriendlyIdentifier = (string)HttpContext.Current.Session["FriendlyIdentifier"];

    string realm = (string)Session["Realm"];
    string dataSource = (string)Session["DataSource"];
    string accessToken = (string)Session["AccessToken"] + "";
    string accessTokenSecret = (string)Session["AccessTokenSecret"];
    string invalidAccessToken = (string)Session["InvalidAccessToken"];
}

<h1>Intuit - QuickBooks Online</h1>

<div class="well">
    @if (FriendlyName.Length == 0)
    {
        <script>
            window.location.href = "/QuickBooks/OpenIdIndex";
        </script>
    }
    else
    {
        <div id="IntuitInfo">
            <strong>Open Id Information:</strong><br />
            Welcome: @FriendlyName<br />
            E-mail Address: @FriendlyEmail<br />
            <br />
            @if (accessToken.Length > 0 && invalidAccessToken == null)
            {
                <div id="oAuthinfo">
                    <a onclick="reconcileInvoices()" id="RecInvoices" class="btn btn-primary">Set Invoices Paid</a><br />
                    <br/>
                    <a onclick="recTax()" id="RecTax" class="btn btn-primary">Sync Tax Rates</a><br />
                    <br/>
                    @if (SessionContext.UseAccountCodes)
                    {
                        <a onclick="recProducts()" id="RecProducts" class="btn btn-primary">Sync Products</a><br />
                        <br />
                    }
                    <a href="/QuickBooks/ReconcileCustomers" id="Customers" class="btn btn-primary">Reconcile Customers</a><br/>
                    <br/>
                    <a href="/QuickBooks/Disconnect" id="Disconnect" class="btn btn-primary">
                        Disconnect from
                        QuickBooks
                    </a>
                    <br/><br/>
                    @*<a href="/QuickBooks/Customers" id="QBCustomers" class="btn btn-primary">Get QuickBooks Customer List</a><br/>
        <br/>*@

                    @*<br />
        <a href="/QuickBooks/Invoices" id="QBInvoices" class="btn btn-primary">Get QuickBooks Invoice List</a><br />
        <br />*@
                    <br />
                </div>
            }
            else
            {
                <br />
                    <ipp:connecttointuit></ipp:connecttointuit>
            }
        </div>
    }
</div>
<script type="text/javascript" src="https://js.appcenter.intuit.com/Content/IA/intuit.ipp.anywhere-1.3.5.js"></script>
<script type="text/javascript">
    intuit.ipp.anywhere.setup({
        menuProxy: '@MenuProxy',
        grantUrl: '@OauthGrant'
    });
</script>

【讨论】:

  • 非常感谢 - 非常感谢您的帮助。
  • 如果您想对此进行任何澄清,请在评论中询问
  • 谢谢 - 我正在构建一个 webforms 应用程序以在 apps.com 上发布,这对于我经验有限的人来说非常棘手。感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-03-14
  • 2016-03-06
  • 2014-12-23
  • 1970-01-01
  • 2017-12-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多