Начало работы Xero. Net Core OAuth2 для работы - PullRequest
0 голосов
/ 19 февраля 2020

Добрый день, эксперты,

Нам необходимо создать интеграцию с XERO Api через их новейшие стандарты OAuth2 с последним (вышеупомянутым). NET CORE 3.1 в VS.

Я поливал существующую базу образцов в GitHub за последние 2 дня, даже не достигнув каких-либо точек аутентификации. Вот где я сейчас застрял: просто заставляю мое приложение проходить аутентификацию.

Я прибег к загрузке приведенного выше примера непосредственно из GitHub и вводу (по крайней мере из того, что я вижу). ) только две переменные, необходимые для этой работы: ClientID и ClientSecret (в настройки приложений. json). Приложение также зарегистрировано под MyApps в Xero с правильными ClientID и ClientSecret.

Моя среда довольно проста, как они предполагают в примере приложения: Запуск этого с localhost: 5000, и зарегистрируйте то же самое под своими MyApps в ксеро. За исключением, говорят, зарегистрируйте свои URL-адреса перенаправления OAuth2 как

http://localhost: 5000 / signup-oid c

. NET Мне кажется, что CORE это не нравится, поэтому я имею их как

http://localhost: 5000 / signup_oid c

Поэтому, когда я запускаю это, мне представляются стандартные 2 кнопки Xero (SignUp & SignIn), которые уже были объявлены в представлении. enter image description here

Нажмите кнопку Xero SignIn, которая должна выстрелить:

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

Но не (правильно), так как мой личность пользователя еще не аутентифицирована. Это (в соответствии со схемой аутентификации Xero) приводит меня к конечной точке идентификации Xero. (как проверено через POSTMAN) (https://login.xero.com/identity/connect/authorize, содержащий мой ClientID, URL реферала и области в качестве параметров)

Проблема в том, что я продолжаю получать это : Xero Error page

Вещи, которые я проверял / пробовал:

  1. Проверено, что мои appsettings. json обнаружены и ClientID / Secret корректно загружается при запросе в Startup.cs
  2. Обновлен CallbackPath в Startup.cs до "/ signin_oid c"
  3. Изменение областей действия
  4. Добавление моего clientID & Secret в XeroClient в разных точках, чтобы убедиться, что оно сохраняется.
  5. буквально просматривая все помеченные сообщения [Xero-Api] на SO
  6. чтение примера проекта Xero README несколько раз over.

На этом этапе я должен быть представлен на странице входа в 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");
    }
}

Любой совет будет с благодарностью!

1 Ответ

2 голосов
/ 21 февраля 2020

В примере есть две разные схемы аутентификации, использующие два разных URL-адреса обратного вызова; один из которых оканчивается идентификатором входа c, а другой оканчивается идентификатором регистрации c.

Необходимо убедиться, что вы зарегистрировали оба URL-адреса обратного вызова, чтобы образец работал полностью, а также Вы обнаружили, что на портале для разработчиков и в вашем коде должно быть точно такое же, уделяя особое внимание тому, чтобы убедиться, что порты совпадают между запущенным примером и URL-адресами обратного вызова, которые вы регистрируете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...