Я пытаюсь выполнить поиск основной пользовательской информации в Azure Active Directory в установленном приложении ASP.NET MVC с использованием Microsoft Graph SDK. Не обнаружив упоминаний об использовании этих двух элементов вместе и о том, что мои собственные попытки реализации не увенчались успехом, я задаюсь вопросом, сделал ли это кто-то еще, или известно, что это невозможно.
В настоящее время наше приложение размещено в службе приложений Azure (xyz.azurewebsites.net
), которая настроена на проверку подлинности всех запросов перед доступом к приложению. Я понимаю, что это называется «Easy Auth», как задокументировано здесь или здесь .
В качестве теста я более или менее выполнил шаги, описанные в этого учебного пособия , понимая, что оно предназначено для регистрации приложения Azure Active Directory. Я добавил контроллер barebones в проект, который вызвал еще одну проблему аутентификации, например:
public void Index()
{
// Signal OWIN to send an auth request to Azure
Request.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
Задача аутентификации может быть успешно завершена (даже если пользователь авторизован уже через xyz.azurewebsites.net
). Проверка ответа после завершения вызова показывает, что я получаю ответ code
в данных формы. Однако код, который должен выполнить и обработать успешную аутентификацию, никогда этого не делает (он показан внизу этого поста).
По сути, текущий поток таков:
- Пользователь переходит на
xyz.azurewebsites.net
xyz.azurewebsites.net
видит, что пользователь не аутентифицирован, и переносит их на login.microsoftonline.com/{tenant}/oauth2/authorize
- Пользователь вводит свои учетные данные для AAD и регистрируется, попадая на корневую страницу
xyz.azurewebsites.net
Все, что за этим пунктом, тестируется
- Для тестирования я перехожу к
xyz.azurewebsites.net/signin
, где запускается вышеуказанный метод Index()
(подтверждено удаленной отладкой)
- Задача перенаправляет меня на
login.microsoftonline.com/common/oauth2/v2.0/authorize
, и я снова ввожу учетные данные
- После успешного входа в систему меня перенаправили на
xyz.azurewebsites.net
и сообщили, что «у вас нет разрешения на просмотр этого каталога или страницы». Метод, который я ожидаю встретить при успешной аутентификации, никогда не запускается (подтверждено удаленной отладкой)
- После перехода в другие местоположения на сайте я могу получить доступ к этим страницам, что говорит о том, что я все еще аутентифицирован с помощью «Easy Auth». Мой метод проверки наличия доступа к Microsoft Graph тоже не работает.
Для тестирования мне хотелось бы, чтобы пользователь снова вводил учетные данные, успешно перенаправлялся на домашнюю страницу и контроллеры могли каким-либо образом получать доступ к Microsoft Graph.
Идеи
Некоторые идеи о том, что может помешать успеху:
Возможно, что то, о чем я прошу, просто не может быть сделано, и нам придется атаковать это под другим углом.
Я думаю, что ошибка отказа в разрешении, описанная в шаге 6, связана с обратным вызовом из вызова Graph, не включающим токен доступа, который обычно добавляется в заголовки запроса с помощью Easy Auth. Таким образом, последующие запросы будут перехватываться программой Easy Auth и работать, но поскольку первоначальный обратный вызов не выполнен, код, который, как ожидается, будет выполнен при успешном вызове, не будет обработан.
Если возможно, возможно, необходимо настроить службу приложения для возврата маркеров доступа для Microsoft Graph.
Код запуска OWIN
using Owin;
using Microsoft.Graph;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Notifications;
using Microsoft.Owin.Security.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Identity.Client;
using System.Configuration;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
using xyz.Utilities;
[assembly: OwinStartup(typeof(xyz.Startup))]
namespace xyz
{
public class Startup
{
private static string appId = ConfigurationManager.AppSettings["AzureActiveDirectoryAppId"];
private static string appSecret = ConfigurationManager.AppSettings["AzureActiveDirectoryAppSecret"];
private static string redirectUri = ConfigurationManager.AppSettings["AzureActiveDirectoryRedirectUri"];
private static string graphScopes = ConfigurationManager.AppSettings["AzureActiveDirectoryGraphScopes"];
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
// This executes upon startup and configures the app.
}
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = appId,
Authority = "https://login.microsoftonline.com/common/v2.0",
Scope = $"openid email profile offline_access {graphScopes}",
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false // May need to be changed. See https://docs.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.tokens.tokenvalidationparameters?view=azure-dotnet
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailedAsync,
AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync
}
}
);
}
private Task OnAuthenticationFailedAsync(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
throw new System.Exception("Authentication Failed"); // This may need to be changed to redirect the user so they can try again.
}
private async Task OnAuthorizationCodeReceivedAsync(AuthorizationCodeReceivedNotification notification)
{
// This function does not execute once authentication is complete through /SignIn
var idClient = ConfidentialClientApplicationBuilder.Create(appId)
.WithRedirectUri(redirectUri)
.WithClientSecret(appSecret)
.Build();
var signedInUser = new ClaimsPrincipal(notification.AuthenticationTicket.Identity);
var tokenStore = new SessionTokenStore(idClient.UserTokenCache, HttpContext.Current, signedInUser);
string[] scopes = graphScopes.Split(' ');
var result = await idClient.AcquireTokenByAuthorizationCode(scopes, notification.Code).ExecuteAsync();
User userDetails = await GraphUtility.GetUserDetailAsync(result.AccessToken);
string email = string.IsNullOrEmpty(userDetails.Mail) ? userDetails.UserPrincipalName : userDetails.Mail;
var cachedUser = new CachedUser()
{
DisplayName = userDetails.DisplayName,
Email = string.IsNullOrEmpty(userDetails.Mail) ? userDetails.UserPrincipalName : userDetails.Mail
};
tokenStore.SaveUserDetails(cachedUser);
}
}
}