【问题标题】:CORS error with MSAL, Angular and ASP.NET CoreMSAL、Angular 和 ASP.NET Core 的 CORS 错误
【发布时间】:2020-01-06 02:33:58
【问题描述】:

我正在尝试构建一个 ASP.NET Core webapi + Angular 网站,用户可以在其中使用 Microsoft 个人或工作或学校电子邮件登录。我按照此处描述的说明进行操作: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-angular/README.md

但是遇到这个问题,

  1. 角度网站加载,主页上受保护的组件触发登录过程

  2. 微软网站出现

  3. 我登录

  4. 浏览器加载以下网址:https://localhost:44321/#id_token=...&state=...

  5. 网站重新加载(登录后第二次)

  6. 我看到以下错误

    在“https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=...”处访问 XMLHttpRequest(重定向自“https://localhost:44321/Environment/GetUserInfo” )来自原点“https://localhost:44321”已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查:请求的资源上不存在“Access-Control-Allow-Origin”标头。

使用 Visual Studio 2019 16.4.2 和 chrome 79.0.3945.88 进行本地调试时

有什么想法吗?
谢谢

我已经使用

创建了我的项目
dotnet new angular -o myapp

并在 Azure.Portal 中创建了应用注册
使用以下重定向 URI 进行身份验证

  • https://localhost:44321/signin-microsoft
  • https://login.microsoftonline.com/
  • http://localhost:30662/
  • https://localhost:44321/signin-oidc
  • https://localhost:44321/

检查了所有“公共客户端的建议重定向 URI”
注销网址:https://localhost:44321/signout-callback-oidc
隐式授权:访问令牌和 ID 令牌
实时 SDK 支持:是
默认客户端类型:否

证书和秘密
我创建了一个客户端密码,因为我尝试使用 Microsoft 提供程序(请参阅下面的注释代码)然后尝试使用 AzureAd

API 权限
Microsoft.Graph User.Read

公开一个 API
范围 = [应用程序 ID URI]/access_as_user,仅限管理员
客户端应用程序 = [CLIENT_ID_FROM_AZURE_PORTAL],以上范围

服务器端
appsettings.json

 "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "[Application ID URI]",
    "ClientId": "[CLIENT_ID_FROM_AZURE_PORTAL]",
    "TenantId": "common",
    "CallbackPath": "/signin-oidc"
  },

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
  services.AddCors(options =>
  {
    options.AddPolicy("AllowAllOrigins",
        builder =>
        {
          builder
            .AllowAnyMethod()
            .AllowAnyHeader()
            .AllowAnyOrigin();
        });
  });

  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;
  });

  // In production, the Angular files will be served from this directory
  services.AddSpaStaticFiles(configuration =>
  {
    configuration.RootPath = "ClientApp/dist";
  });

  //services
  //  .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
  //  .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
  //  .AddMicrosoftAccount(microsoftOptions =>
  //{
  //  microsoftOptions.ClientId = "[CLIENT_ID_FROM_AZURE_PORTAL]";
  //  microsoftOptions.ClientSecret = "[CLIENT_SECRET_FROM_AZURE_PORTAL]";
  //});

  services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
    .AddAzureAD(options => Configuration.Bind("AzureAd", options));

  services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
  {
    options.Authority = options.Authority + "/v2.0/";
    options.TokenValidationParameters.ValidateIssuer = false;
  });

  services.AddControllers(options =>
  {
    var policy = new AuthorizationPolicyBuilder()
                    .RequireAuthenticatedUser()
                    .Build();
    options.Filters.Add(new AuthorizeFilter(policy));
  })
  .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
  app.UseCors("AllowAllOrigins");

  if (env.IsDevelopment())
  {
    app.UseDeveloperExceptionPage();
  }
  else
  {
    app.UseExceptionHandler("/Error");
    app.UseHsts();
  }

  app.UseHttpsRedirection();
  app.UseCookiePolicy();
  app.UseStaticFiles();
  if (!env.IsDevelopment())
  {
    app.UseSpaStaticFiles();
  }

  app.UseRouting();

  app.UseAuthentication();
  app.UseAuthorization();

  app.UseEndpoints(endpoints =>
  {
    endpoints.MapControllers();
  });

  app.UseSpa(spa =>
  {
    spa.Options.SourcePath = "ClientApp";

    if (env.IsDevelopment())
    {
      spa.UseAngularCliServer(npmScript: "start");
    }
  });
}

客户端
app.module.ts

...
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { MsalModule, MsalGuard, MsalInterceptor } from '@azure/msal-angular';
...
export const protectedResourceMap: [string, string[]][] = [
  ['https://localhost:44321/Environment/GetUserInfo', ['[Application ID URI]/access_as_user']],
  ['https://localhost:44321/api/Environment/GetUserInfo', ['[Application ID URI]/access_as_user']],
  ['https://graph.microsoft.com/v1.0/me', ['user.read']],
  ['https://login.microsoftonline.com/common', ['user.read']]
];

@NgModule({
  declarations: [
    AppComponent,
    NavMenuComponent,
    HomeComponent,
    CounterComponent,
    EntitySignoffComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
    HttpClientModule,
    FormsModule,
    AgGridModule.withComponents([]),
    MsalModule.forRoot({
      clientID: [CLIENT_ID_FROM_AZURE_PORTAL],
      authority: "https://login.microsoftonline.com/common",
      redirectUri: "https://localhost:44321/",
      validateAuthority: true,
      cacheLocation: "localStorage",
      storeAuthStateInCookie: false, // dynamically set to true when IE11
      postLogoutRedirectUri: "https://localhost:44321/",
      navigateToLoginRequestUrl: true,
      popUp: false,
      unprotectedResources: [ "https://login.microsoftonline.com/common" ],
      protectedResourceMap: protectedResourceMap
    }
    ),
    RouterModule.forRoot([
      { path: '', component: HomeComponent, pathMatch: 'full', canActivate: [MsalGuard] }
      { path: 'counter', component: CounterComponent, canActivate: [MsalGuard] },
    ])
  ],
  providers: [NavMenuComponent, { provide: HTTP_INTERCEPTORS, useClass: MsalInterceptor, multi: true }],

package.json

{
  "name": "myapp",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "build:ssr": "ng run myapp:server:dev",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "8.2.14",
    "@angular/common": "^8.2.14",
    "@angular/compiler": "8.2.14",
    "@angular/core": "^8.2.14",
    "@angular/forms": "8.2.14",
    "@angular/platform-browser": "8.2.14",
    "@angular/platform-browser-dynamic": "8.2.14",
    "@angular/platform-server": "8.2.14",
    "@angular/router": "8.2.14",
    "@azure/msal-angular": "^0.1.4",
    "@nguniversal/module-map-ngfactory-loader": "8.2.6",
    "aspnet-prerendering": "^3.0.1",
    "bootstrap": "^4.4.1",
    "core-js": "^3.6.1",
    "jquery": "3.4.1",
    "oidc-client": "^1.10.1",
    "popper.js": "^1.16.0",
    "rxjs": "^6.5.4",
    "rxjs-compat": "^6.5.4",
    "zone.js": "0.10.2"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^0.803.21",
    "@angular/cli": "8.3.21",
    "@angular/compiler-cli": "8.2.14",
    "@angular/language-service": "8.2.14",
    "@types/jasmine": "^3.5.0",
    "@types/jasminewd2": "~2.0.8",
    "@types/node": "~13.1.2",
    "codelyzer": "^5.2.1",
    "jasmine-core": "~3.5.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "^4.4.1",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage-istanbul-reporter": "^2.1.1",
    "karma-jasmine": "~2.0.1",
    "karma-jasmine-html-reporter": "^1.5.1",
    "typescript": "3.5.3"
  },
  "optionalDependencies": {
    "node-sass": "^4.13.0",
    "protractor": "~5.4.2",
    "ts-node": "~8.5.4",
    "tslint": "~5.20.1"
  }
}

Error in Chrome after login

【问题讨论】:

  • 请检查问题是否是由same one discussed in this github issue引起的。
  • 我已经更新到这些线路 services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme) .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));但是现在我得到一个 401,它在登录 HttpErrorResponse {headers: HttpHeaders, status: 401, statusText: "OK", url: "localhost:44321/Environment/GetUserInfo", ok: false, ...} 后仍然加载了两次网页
  • 您好,您的问题现在解决了吗?
  • 嗨,Stanley,不,我也有同样的问题,请查看“登录后 Chrome 中的错误”上方的图片

标签: angular asp.net-core azure-active-directory msal msal.js


【解决方案1】:

据我了解,这两个官方样品将完全满足您的要求。

我已经修改了官方demo并为你集成了。请按照以下步骤使它们工作:

  1. 对于 API 端 .You can download my code here。

1) 进入 Azure 门户并为其注册一个 Azure AD 应用程序:

2) 记下它的应用程序 ID 并为其创建一个客户端密码。将它们输入 appsettings.json :

3) 返回 Azure 门户,公开一个 API,以便客户端可以请求它的权限:

4)API端的工作已经完成,你可以直接运行这个项目了。

  1. 角边,可以get my code here。

1) 为此客户端注册一个 Azure AD 应用程序:

2) 向该应用添加权限,以便它可以访问您的后端: 单击授予权限按钮以完成该过程: 这两个演示都基于 Azure AD V2.0 端点,允许个人帐户和学校工作帐户登录并执行一些操作。

3) 在身份验证刀片上进行一些配置,以便它可以获得作为公共客户端所需的令牌:

4) 所有步骤都针对 angular side 完成,npm start 运行它,访问它:http://localhost:44302/ 我已经在本地进行了测试,它对我来说非常有效:

如果你不知道如何结合这两个应用程序,请告诉我。

【讨论】:

  • 我尝试合并这些应用程序,但在客户端出现以下错误:错误:login_required;错误描述:AADSTS50058:已发送静默登录请求,但没有用户登录。用于表示用户会话的 cookie 未在请求中发送到 Azure AD。如果用户使用 Internet Explorer 或 Edge,并且发送静默登录请求的 Web 应用位于与 Azure AD 终结点不同的 IE 安全区域,则可能会发生这种情况。我正在使用 chrome,如果可能的话,您能否上传您的版本,以便我可以将合并排除为潜在问题
  • 嗨@D2O,我已经用详细的步骤更新了我的答案,请检查一下
  • 嗨,Stanley,抱歉耽搁了,我按照你的步骤做了一些额外的任务;对于服务器,我必须从 github 克隆 Microsoft.Identity.Web 项目并引用它;在客户端,我必须使用 clientID 和 webApiScores 更新 app.js。现在网站登录和注销,但是当我调用 api 我仍然有这个错误:在响应中出现错误:{“method”:“POST”,“transformRequest”:[null],“transformResponse”:[null], "jsonpCallbackParam":"callback","url":"localhost:44351/api/…: 发送了静默登录请求,但没有用户登录
  • 但是我从你的步骤中学到的一件事是服务器和客户端有单独的注册,我不知道,我虽然这是一个注册。我的变化还好吗。有没有可能我错过了其他东西。感谢您的帮助
  • 我在浏览器中添加了一个带有错误和注销按钮的快照的链接
【解决方案2】:

这个问题没有太多的赞成票,但我在使用 MSAL 时也遇到了 CORS 错误。对我来说,这是由于为 @azure/msal-angular 选择的身份验证方案与后端预期的身份验证方案不匹配造成的。

就我而言,两者都需要是 JWTBearer 或 'Bearer {token}'。

您获得 CORS 的原因是不匹配会触发后端尝试与 microsoft 进行身份验证,从而导致重定向(和跨域请求)。通过调整前端和后端身份验证方案,问题就迎刃而解了。

【讨论】:

    猜你喜欢
    • 2021-06-21
    • 2019-08-22
    • 1970-01-01
    • 2019-10-17
    • 1970-01-01
    • 2020-09-10
    • 2021-08-22
    • 2019-08-29
    • 1970-01-01
    相关资源
    最近更新 更多