Доступ к MS Graph API с напрямую полученным токеном - PullRequest
1 голос
/ 20 марта 2019

Мой проект основан на примере от имени потока .

В моем веб-интерфейсе API у меня есть неограниченный метод [Authorize], который получает логин и пароль,У меня также есть ограниченный метод, который получает некоторую информацию из MS Graph API:

[HttpGet]
[Authorize]
[Route("[action]")]
public async Task<IActionResult> Info()
{
    string realAccessToken = await _tokenAcquisition.GetAccessTokenOnBehalfOfUser(HttpContext, scopes).ConfigureAwait(false);
    var userInfoJson = await AzureGraphDataProvider.GetCurrentUserAsync(realAccessToken).ConfigureAwait(false);
...
}

В неограниченном методе я отправляю запрос в Azure и получаю информацию обратно с помощью токена доступа.Затем я пытаюсь вызвать метод Graph Api напрямую, используя этот токен, но получаю Unauthorize .

Так как от имени потока получает User от HttpContext, я получаю ошибку выше, только когда яполучить доступ к неограниченному методу [authorize] и вызвать Graph API напрямую из него, используя полученный токен.

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

Есть идея получше, пожалуйста?

[HttpPost]
[Route("[action]")]
public async Task<IActionResult> GetAzureOAuthData([FromBody]dynamic parameters)
{
    string userName = parameters.userName.ToString();
    string password = parameters.password.ToString();
    using (HttpClient client = new HttpClient())
    {
        var oauthEndpoint = new Uri("https://login.microsoftonline.com/organizations/oauth2/v2.0/token");
        var result = await client.PostAsync(oauthEndpoint, new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("client_id", _azureOptions.ClientId),
            new KeyValuePair<string, string>("grant_type", "password"),
            new KeyValuePair<string, string>("username", userName),
            new KeyValuePair<string, string>("password", password),
            new KeyValuePair<string, string>("client_secret", _azureOptions.ClientSecret),
            new KeyValuePair<string, string>("client_info", "1"),
            new KeyValuePair<string, string>("scope",
                "openid offline_access profile api://xxxxxxxxxx-xxxxxxxx-xxxxxxxxx/access_as_user api://xxxxxxxxxx-xxxxxxxx-xxxxxxxxx/access_as_admin"),
        })).ConfigureAwait(false);

        var content = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
        var oar = JsonConvert.DeserializeObject<AzureOAuthResult>(content);

        string[] scopes = { "user.read" };

       // doesn't work, unauthorized
       var currentUserContent = await AzureGraphDataProvider.GetCurrentUserAsync(oar.AccessToken).ConfigureAwait(false);
            // It works, but have to call web api method
            using (HttpClient client2 = new HttpClient())
            {
                client2.DefaultRequestHeaders.Add("Authorization", $"Bearer {oar.AccessToken}");
                var currentUserResult = await client2.GetAsync($"{Request.Scheme}://{Request.Host}/api/users/userinfo");
                var currentUserContent = await currentUserResult.Content.ReadAsStringAsync().ConfigureAwait(false);
            }

        return Ok(...);
    }
}

В AzureGraphDataProvider:

public static async Task<string> GetCurrentUserAsync(string accessToken)
{
    HttpClient client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    HttpResponseMessage response = await client.GetAsync(new Uri("https://graph.microsoft.com/v1.0/me")).ConfigureAwait(false);
    if (response.StatusCode == HttpStatusCode.OK)
    {
        return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
    }

    return string.Empty;
}

1 Ответ

3 голосов
/ 20 марта 2019

Не используйте предоставление пароля ROPC и не собирайте имя пользователя / пароль непосредственно в вашем API

Я вижу, что вы ожидаете, что имя пользователя и пароль будут предоставлены непосредственно вашему API для получения токена с использованием ROPC или предоставления пароля. Это нарушает рекомендации по безопасности, а также имеет функциональные ограничения, например, не работает с MFA. Вы можете посмотреть этот пост для аналогичного обсуждения, и есть много других ресурсов, которые подтвердят то же самое для вас. Здесь - старая статья, но все еще очень подробная. И посмотрите на длинный список ограничений в конце.

Я ссылаюсь на этот код, которым вы поделились:

public async Task<IActionResult> GetAzureOAuthData([FromBody]dynamic parameters)
{
    string userName = parameters.userName.ToString();
    string password = parameters.password.ToString();
    using (HttpClient client = new HttpClient())
    {
        var oauthEndpoint = new Uri("https://login.microsoftonline.com/organizations/oauth2/v2.0/token");
        var result = await client.PostAsync(oauthEndpoint, new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("client_id", _azureOptions.ClientId),
            new KeyValuePair<string, string>("grant_type", "password"),
            new KeyValuePair<string, string>("username", userName),
            new KeyValuePair<string, string>("password", password),

Причина, по которой ваш неограниченный метод не работает

Вы получаете токен для своего собственного API, используя предоставление пароля. Тогда вы на самом деле не используете «От имени потока» для приобретения токена для Microsoft Graph от имени пользователя, вызвавшего ваш API.

Таким образом, токен действителен для вашего API, но не для Microsoft Graph API, и, следовательно, вы получаете ошибку UnAuthorized.

Другой метод, который вы называете ограниченным методом, выполняет поток от имени, сначала получая токен для Microsoft Graph API от имени пользователя.

string realAccessToken = await _tokenAcquisition.GetAccessTokenOnBehalfOfUser(HttpContext, scopes).ConfigureAwait(false);

Лучшие идеи, как вы просите об этом

Клиентское приложение, которое вызывает ваш API, должно использовать делегированные разрешения и потоки OAuth, такие как поток предоставления кода авторизации или другие, в зависимости от вашего сценария, чтобы получить токен доступа для вашего API.

API может затем использовать поток On-Behalf-Of, как вы делаете это в ограниченном методе, чтобы получить токен, требуемый Microsoft Graph API.

...