各位专家,
我们需要通过他们最新的OAuth2标准与XERO构建一个集成,其中包含(上面提到的) VS中最新的.NET核心3.1。
在过去的两天里,我一直在GitHub中的现有示例库中大量使用,甚至没有达到任何认证点。--这是我目前陷入困境的地方:让我的应用程序进行身份验证。
我已经直接从GitHub下载了上面的示例,并输入了(至少从我所看到的)唯一需要的两个变量:ClientID和ClientSecret (进入appsettings.json)。该应用程序还在Xero的MyApps下注册,并具有正确的ClientID和ClientSecret。
我的环境非常简单,就像他们在示例应用程序中假设的那样:从localhost:5000运行这个程序,并在您的MyApps下用Xero注册相同的环境。但是,他们说,注册您的OAuth2重定向URLS为
http://localhost:5000/signup-oidc
.NET核心似乎不喜欢这样,所以我把它们作为
奥伊达
因此,当我运行它时,我会看到已经在视图中声明的标准2 Xero按钮(SignUp & SignIn)。

单击SignIn Xero按钮,应该会触发:
[HttpGet]
[Route("signin")]
[Authorize(AuthenticationSchemes = "XeroSignIn")]
public IActionResult SignIn()
{
return RedirectToAction("OutstandingInvoices");
}但是没有,(正确地说),因为我的用户身份还没有被验证。这(根据Xero的身份验证方案)将我带到Xero的身份端点。(通过邮递员检查)(https://login.xero.com/identity/connect/authorize包含我的ClientID、引用URL和范围为params)
的问题是,我一直得到这样的信息:

我检查过/尝试过的东西:
在这个阶段,我应该在Xero的登录页面中显示,要求我登录到我的Xero帐户,然后要求我授权我的应用程序申请的范围,然后重定向回我的应用程序。(至少是第一次)。
在这个阶段有点不知所措,不知我错过了什么。
请参阅Startup.cs
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
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");
}
}任何建议都将不胜感激!
发布于 2020-02-20 21:44:45
在示例中,有两个不同的auth方案,在示例中使用两个不同的回调urls;一个以signin-oidc结尾,另一个以注册-oidc结尾。
您需要确保为示例注册两个回调urls才能完整工作,正如您已经发现的,开发人员门户和代码中的需求完全相同,特别要注意确保运行的示例和注册的回调urls之间的端口是相同的。
https://stackoverflow.com/questions/60299507
复制相似问题