Comment configurer l'authentification Azure AD en hybride ASP.NET Core MVC (backend) et Vuejs SPA (frontend) ?
P粉242741921
P粉242741921 2024-01-08 10:44:05
0
1
572

Mon application est une approche hybride utilisant ASP.NET Core MVC comme backend. J'ai divers contrôleurs que mon interface utilise pour extraire des données de la base de données et effectuer des appels API sur MS Graph. J'utilise le fichier program.cs suivant pour lancer l'authentification lorsqu'un utilisateur se connecte au site Web pour la première fois :

//authentication pipline
builder.Services.AddHttpContextAccessor();
var initialScopes = builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApp(options =>
                {
                    builder.Configuration.Bind("AzureAd", options);
                    options.Events = new OpenIdConnectEvents
                    {
                        //Tap into this event to add a UserID Claim to a new HttpContext identity
                        OnTokenValidated = context =>
                        {
                            //This query returns the UserID from the DB by sending the email address in the claim from Azure AD
                            string query = "select dbo.A2F_0013_ReturnUserIDForEmail(@Email) as UserID";
                            string connectionString = builder.Configuration.GetValue<string>("ConnectionStrings:DBContext");
                            string signInEmailAddress = context.Principal.FindFirst   Value("preferred_username");

                            using (var connection = new SqlConnection(connectionString))
                            {
                                var queryResult = connection.QueryFirst(query, new { Email = signInEmailAddress });

                                var claims = new List<Claim>
                                {
                                    new Claim("UserID", queryResult.UserID.ToString())
                                };

                                var appIdentity = new ClaimsIdentity(claims);

                                context.Principal.AddIdentity(appIdentity);
                            }

                            return Task.CompletedTask;
                        },
                    };
                }).EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
                        .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
                        .AddInMemoryTokenCaches();

//Add Transient Services
builder.Services.AddTransient<IOneDrive, OneDrive>();

builder.Services.AddControllers(options =>
{
    var policy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
    options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();

builder.Services.AddRazorPages().AddRazorPagesOptions(options =>
{
    options.Conventions.AllowAnonymousToFolder("/Login");
    options.Conventions.AuthorizeFolder("/");
    options.Conventions.AuthorizeFolder("/files");
}).AddMicrosoftIdentityUI();

// Add the UI support to handle claims challenges
builder.Services.AddServerSideBlazor()
   .AddMicrosoftIdentityConsentHandler();
builder.Services.AddRequiredScopeAuthorization();

Dans le portail Azure AD, mon application est enregistrée en tant qu'application web. Par conséquent, lorsque les utilisateurs accèdent pour la première fois au site Web, ils sont redirigés vers https://login.microsoftonline.com/blahblah pour commencer le processus de connexion. Ceci est effectué automatiquement par la plateforme d’identité Azure AD. Ensuite, une fois la connexion effectuée, ils sont redirigés vers l'hôte local (localhost:43862) où le spa VueJS est chargé. Mon spa utilise diverses requêtes axios aux contrôleurs, qui extraient des données, et au routeur vue pour charger les composants. Cependant, mon problème est que l'utilisateur doit se reconnecter car le cookie a expiré ou s'il s'est déconnecté dans un autre onglet. La prochaine requête axios d'une session expirée ne redirige pas l'utilisateur vers l'écran de connexion Azure mais entraîne une erreur CORS. J'ai donc besoin que ma requête axios force la page à rediriger vers l'écran de connexion Azure AD (ce qui est probablement la pire idée puisque la politique CORS provoquera des erreurs), ou qu'elle soit redirigée vers localhost/login, ce que je fais. votre propre écran de connexion personnalisé à l’aide du bouton de connexion Azure AD ne devrait pas affecter CORS. Alors, comment puis-je intercepter cette redirection Azure AD vers la connexion Azure AD et la remplacer par la mienne ?

J'ai également essayé de renvoyer un code d'erreur 401 pour pouvoir le vérifier dans la requête axios, mais en vain, cela ne fait rien. Si j'y mets un point d'arrêt, il atteint ce code, mais cela ne change pas le code d'état de la réponse, j'obtiens toujours un 302. Mon code essaie d'ajouter à l'événement :

OnRedirectToIdentityProvider = context =>
                    {
                        context.Response.StatusCode = 401;
                        return Task.CompletedTask;
                    }

Mes autres pensées sont peut-être que je devrais configurer la politique CORS pour autoriser les redirections depuis login.microsoft.com ? Ou est-ce une mauvaise pratique ?

P粉242741921
P粉242741921

répondre à tous(1)
P粉501007768

Je peux répondre à une partie de vos questions... Tout d'abord, pour notre application API protégée Azure AD, ce que l'API doit faire est de vérifier que la requête contient le bon jeton d'accès dans l'en-tête de la requête, et si oui, de donner une réponse, si non, une erreur telle que 401 ou 403 est donnée. Une application API normale ne devrait pas avoir d'interface utilisateur pour connecter les utilisateurs. Quoi qu'il en soit, si vous souhaitez exposer une API dans un projet MVC, ce n'est pas grave, mais pour l'API elle-même, elle ne devrait pas avoir d'interface utilisateur.

Voyons l'exemple ci-dessous, j'ai un projet d'API Web .net 6 et voici mon program.cs :

using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration);
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

Et doit être configuré dans appsetting.json.

"AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "ClientId": "azure_ad_client_id",
    "ClientSecret": "client_secret",
    "Domain": "tenant_id",
    "TenantId": "tenant_id",
    //"Audience": "api://azure_ad_client_id_which exposed_api" // here I used the same azure ad app to expose API, so I can comment this property
  },

Voici le contrôleur :

[ApiController]
    [Route("[controller]")]
    [Authorize]
    public class WeatherForecastController : ControllerBase
    {
        [RequiredScope("Tiny.Read")]
        [HttpGet]
        public string Get()
        {
            return "world";
        }
    }

J'ai une application Azure AD et j'expose l'API suivante :

J'ai également ajouté cette API pour la même application Azure AD.

Alors faisons un test. Lorsque j'appelle directement cette API, j'obtiens une erreur 401 :

Je reçois également une erreur 401 si j'utilise un token expiré dans la requête :

Mais si j'utilise le bon jeton (allez sur https://jwt.io pour décoder le jeton, nous devrions voir qu'il contient la bonne portée, pour moi c'est "scp": "Tiny.Read",) et j'obtiens la réponse :

À ce stade, la partie API est terminée. Regardons le SPA client. Pour SPA, vous devez l'intégrer MSAL,以便您可以让您的用户通过 Azure AD 登录,并生成用于调用 MS graph API 或您自己的 API 的访问令牌。生成访问令牌的代码应该相同,但您应该为不同的API设置不同的范围。在我的场景中,我的 API 需要一个范围 Tiny.Read puis je devrais le définir dans mon application client.

Ceci est une capture d'écran de génération du jeton d'accès en réaction. Vous devez définir la portée dans votre code.

Maintenant que vous disposez d'un moyen de générer un jeton d'accès, vous connaissez déjà l'URL de l'API. Ensuite, vous pouvez envoyer une requête pour appeler l'API, utiliser AJAX, utiliser fetch ou autre, envoyez simplement une requête http. Et dans le cadre de l'appel de l'API, la réponse doit également être traitée. Si le code de réponse est 401, vous devez alors effectuer une certaine logique, éventuellement en redirigeant vers la page de connexion. Vous avez dit que vous rencontriez des problèmes ici, que vous rencontriez un problème CORS. Je ne peux pas répondre à cette partie. Je pense que cela dépend de la façon dont vous redirigez vers la page de connexion Azure AD. J'ai bien peur que vous puissiez jeter un œil à cet exemple pour comprendre comment connecter un utilisateur et appeler l'API graphique.

Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal