首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使Xero .Net核心OAuth2示例工作

使Xero .Net核心OAuth2示例工作
EN

Stack Overflow用户
提问于 2020-02-19 11:37:37
回答 1查看 896关注 0票数 0

各位专家,

我们需要通过他们最新的OAuth2标准与XERO构建一个集成,其中包含(上面提到的) VS中最新的.NET核心3.1。

在过去的两天里,我一直在GitHub中的现有示例库中大量使用,甚至没有达到任何认证点。--这是我目前陷入困境的地方:让我的应用程序进行身份验证。

我已经直接从GitHub下载了上面的示例,并输入了(至少从我所看到的)唯一需要的两个变量:ClientIDClientSecret (进入appsettings.json)。该应用程序还在Xero的MyApps下注册,并具有正确的ClientID和ClientSecret。

我的环境非常简单,就像他们在示例应用程序中假设的那样:从localhost:5000运行这个程序,并在您的MyApps下用Xero注册相同的环境。但是,他们说,注册您的OAuth2重定向URLS为

http://localhost:5000/signup-oidc

.NET核心似乎不喜欢这样,所以我把它们作为

奥伊达

因此,当我运行它时,我会看到已经在视图中声明的标准2 Xero按钮(SignUp & SignIn)。

单击SignIn Xero按钮,应该会触发:

代码语言:javascript
复制
    [HttpGet]
    [Route("signin")]
    [Authorize(AuthenticationSchemes = "XeroSignIn")]
    public IActionResult SignIn()
    {
        return RedirectToAction("OutstandingInvoices");
    }

但是没有,(正确地说),因为我的用户身份还没有被验证。这(根据Xero的身份验证方案)将我带到Xero的身份端点。(通过邮递员检查)(https://login.xero.com/identity/connect/authorize包含我的ClientID、引用URL和范围为params)

的问题是,我一直得到这样的信息:

我检查过/尝试过的东西:

  1. 检查是否检测到我的appsettings.json,并在Startup.cs中请求时正确加载客户端in /秘密
  2. 将CallbackPath在Startup.cs中更新为"/signin_oidc“
  3. 变化范围
  4. 在不同的点将我的clientID & XeroClient注入到XeroClient中,以确保它被持久化。
  5. 查找所有其他Xero-Api标记的帖子在S.O。
  6. 多次阅读Xero示例项目自述

在这个阶段,我应该在Xero的登录页面中显示,要求我登录到我的Xero帐户,然后要求我授权我的应用程序申请的范围,然后重定向回我的应用程序。(至少是第一次)。

在这个阶段有点不知所措,不知我错过了什么。

请参阅Startup.cs

代码语言:javascript
复制
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpClient();

        services.TryAddSingleton(new XeroConfiguration
        {
            ClientId = Configuration["Xero:ClientId"],
            ClientSecret = Configuration["Xero:ClientSecret"]
        });

        services.TryAddSingleton<IXeroClient, XeroClient>();
        services.TryAddSingleton<IAccountingApi, AccountingApi>();
        services.TryAddSingleton<MemoryTokenStore>();

        services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = "XeroSignIn";
        })
        .AddCookie(options =>
        {
            options.Cookie.Name = "XeroIdentity";

            // Clean up cookies that don't match in local MemoryTokenStore.
            // In reality you wouldn't need this, as you'd be storing tokens in a real data store somewhere peripheral, so they won't go missing between restarts
            options.Events = new CookieAuthenticationEvents
            {
                OnValidatePrincipal = async context =>
                {
                    var tokenStore = context.HttpContext.RequestServices.GetService<MemoryTokenStore>();
                    var token = await tokenStore.GetAccessTokenAsync(context.Principal.XeroUserId());

                    if (token == null)
                    {
                        context.RejectPrincipal(); 
                    }
                }
            };
        })
        .AddOpenIdConnect("XeroSignIn", options =>
        {
            options.Authority = "https://identity.xero.com";

            options.ClientId = Configuration["Xero:ClientId"];
            options.ClientSecret = Configuration["Xero:ClientSecret"];

            options.ResponseType = "code";

            options.Scope.Clear();
            options.Scope.Add("openid");
            options.Scope.Add("profile");
            options.Scope.Add("email");

            options.CallbackPath = "/signin_oidc";

            options.Events = new OpenIdConnectEvents
            {
                OnTokenValidated = OnTokenValidated()
            };
        })
        .AddOpenIdConnect("XeroSignUp", options =>
        {
            options.Authority = "https://identity.xero.com";

            options.ClientId = Configuration["Xero:ClientId"];
            options.ClientSecret = Configuration["Xero:ClientSecret"];

            options.ResponseType = "code";

            options.Scope.Clear();
            options.Scope.Add("offline_access");
            options.Scope.Add("openid");
            options.Scope.Add("profile");
            options.Scope.Add("email");
            options.Scope.Add("accounting.settings");
            options.Scope.Add("accounting.transactions");

            options.CallbackPath = "/signin_oidc";

            options.Events = new OpenIdConnectEvents
            {
                OnTokenValidated = OnTokenValidated()
            };
        });

        services.Configure<CookiePolicyOptions>(options =>
        {
            // This lambda determines whether user consent for non-essential cookies is needed for a given request.
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }

    private static Func<TokenValidatedContext, Task> OnTokenValidated()
    {
        return context =>
        {
            var tokenStore = context.HttpContext.RequestServices.GetService<MemoryTokenStore>();

            var token = new XeroOAuth2Token
            {
                AccessToken = context.TokenEndpointResponse.AccessToken,
                RefreshToken = context.TokenEndpointResponse.RefreshToken,
                ExpiresAtUtc = DateTime.UtcNow.AddSeconds(Convert.ToDouble(context.TokenEndpointResponse.ExpiresIn))
            };

            tokenStore.SetToken(context.Principal.XeroUserId(), token);

            return Task.CompletedTask;
        };
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseStaticFiles();
        app.UseCookiePolicy();

        app.UseAuthentication();

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

}和HomeController.cs

代码语言:javascript
复制
public class HomeController : Controller
{
    private readonly MemoryTokenStore _tokenStore;
    private readonly IXeroClient _xeroClient;
    private readonly IAccountingApi _accountingApi;

    public HomeController(MemoryTokenStore tokenStore, IXeroClient xeroClient, IAccountingApi accountingApi)
    {
        _tokenStore = tokenStore;
        _xeroClient = xeroClient;
        _accountingApi = accountingApi;
    }

    [HttpGet]
    public IActionResult Index()
    {
        if (User.Identity.IsAuthenticated)
        {
            return RedirectToAction("OutstandingInvoices");
        }

        return View();
    }

    [HttpGet]
    [Authorize]
    public async Task<IActionResult> OutstandingInvoices()
    {
        var token = await _tokenStore.GetAccessTokenAsync(User.XeroUserId());

        var connections = await _xeroClient.GetConnectionsAsync(token);

        if (!connections.Any())
        {
            return RedirectToAction("NoTenants");
        }

        var data = new Dictionary<string, int>();

        foreach (var connection in connections)
        {
            var accessToken = token.AccessToken;
            var tenantId = connection.TenantId.ToString();

            var organisations = await _accountingApi.GetOrganisationsAsync(accessToken, tenantId);
            var organisationName = organisations._Organisations[0].Name;

            var outstandingInvoices = await _accountingApi.GetInvoicesAsync(accessToken, tenantId, statuses: new List<string>{"AUTHORISED"}, where: "Type == \"ACCREC\"");

            data[organisationName] = outstandingInvoices._Invoices.Count;
        }

        var model = new OutstandingInvoicesViewModel
        {
            Name = $"{User.FindFirstValue(ClaimTypes.GivenName)} {User.FindFirstValue(ClaimTypes.Surname)}",
            Data = data
        };

        return View(model);
    }

    [HttpGet]
    [Authorize]
    public IActionResult NoTenants()
    {
        return View();
    }

    [HttpGet]
    public async Task<IActionResult> AddConnection()
    {
        // Signing out of this client app allows the user to be taken through the Xero Identity connection flow again, allowing more organisations to be connected
        // The user will not need to log in again because they're only signed out of our app, not Xero.
        await HttpContext.SignOutAsync(); 

        return RedirectToAction("SignUp");
    }

    [HttpGet]
    [Route("signup")]
    [Authorize(AuthenticationSchemes = "XeroSignUp")]
    public IActionResult SignUp()
    {
        return RedirectToAction("OutstandingInvoices");
    }

    [HttpGet]
    [Route("signin")]
    [Authorize(AuthenticationSchemes = "XeroSignIn")]
    public IActionResult SignIn()
    {
        return RedirectToAction("OutstandingInvoices");
    }

    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
        return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }

    [HttpPost]
    [Route("signin_oidc")]
    public IActionResult signin_oidc()
    {
        return RedirectToAction("OutstandingInvoices");
    }
}

任何建议都将不胜感激!

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-02-20 21:44:45

在示例中,有两个不同的auth方案,在示例中使用两个不同的回调urls;一个以signin-oidc结尾,另一个以注册-oidc结尾。

您需要确保为示例注册两个回调urls才能完整工作,正如您已经发现的,开发人员门户和代码中的需求完全相同,特别要注意确保运行的示例和注册的回调urls之间的端口是相同的。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60299507

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档