Получение ошибки CORS от API-шлюза только при добавлении токена Auth0 к запросу

Итак, я пытаюсь разместить полнофункциональное веб-приложение в AWS. У меня есть внешний интерфейс angular, который находится в корзине s3 за облачным фронтом и использует домен от route53. На бэкэнде у меня есть экспресс-проект typescript, который находится за API-шлюзом. Я использую auth0 для аутентификации и примеры приложений для внешнего/внутреннего интерфейса, которые они предоставляют на своих веб-сайтах. Я свяжу их здесь: https://github.com/auth0-developer-hub/spa_angular_typescript_hello-worldhttps://github.com/auth0-developer-hub/api_express_typescript_hello-world

Когда я делаю запросы, не затронутые перехватчиком, они будут работать, но когда я пытаюсь выполнить запросы, которые изменяются перехватчиком (когда я вхожу в систему с использованием auth0), я получаю следующую ошибку: «Доступ к XMLHttpRequest по адресу https://api-gateway.com/dev/api/messages/protected» из источника «https://website.link» заблокирован политикой CORS: ответ на предварительный запрос не проходит проверка управления доступом: в запрошенном ресурсе отсутствует заголовок «Access-Control-Allow-Origin».

Код перехватчика в модуле auth.module ниже:

import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AuthModule, AuthHttpInterceptor } from '@auth0/auth0-angular';
import { environment as env } from '../environments/environment';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { SharedModule } from './shared';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    SharedModule,
    HttpClientModule,
    AuthModule.forRoot({
      ...env.auth0,
      httpInterceptor: {
        allowedList: [`${env.api.serverUrl}/api/messages/admin`, `${env.api.serverUrl}/api/messages/protected`],
        //allowedList: [`${env.api.serverUrl}/api/messages/admin`],
      },
    }),
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthHttpInterceptor,
      multi: true,
    },
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

Ниже приведен мой index.ts из экспресс-бэкэнда, где я явно разрешаю заголовок «Авторизация», который прикрепляет перехватчик. Я подтвердил, что передаваемые токены также действительны.

import cors from "cors";
import * as dotenv from "dotenv";
import * as awsServerlessExpress from 'aws-serverless-express';
import express from "express";
import helmet from "helmet";
import nocache from "nocache";
import { messagesRouter } from "./messages/messages.router";
import { errorHandler } from "./middleware/error.middleware";
import { notFoundHandler } from "./middleware/not-found.middleware";

dotenv.config();

if (!(process.env.PORT && process.env.CLIENT_ORIGIN_URL)) {
  throw new Error(
    "Missing required environment variables. Check docs for more info."
  );
}

const PORT = parseInt(process.env.PORT, 10);
const CLIENT_ORIGIN_URL = process.env.CLIENT_ORIGIN_URL;

const app = express();
const apiRouter = express.Router();

app.use(express.json());
app.set("json spaces", 2);

app.use(
  helmet({
    hsts: {
      maxAge: 31536000,
    },
    contentSecurityPolicy: {
      useDefaults: false,
      directives: {
        "default-src": ["'none'"],
        "frame-ancestors": ["'none'"],
      },
    },
    frameguard: {
      action: "deny",
    },
  })
);

app.use((req, res, next) => {
  res.contentType("application/json; charset=utf-8");
  next();
});
app.use(nocache());

app.use(
  cors({
    origin: CLIENT_ORIGIN_URL,
    methods: ["GET", "POST", "PUT", "DELETE"],
    allowedHeaders: ["Authorization", "Content-Type"],
    maxAge: 86400,
  })
);

app.use("/api", apiRouter);
apiRouter.use("/messages", messagesRouter);

app.use(errorHandler);
app.use(notFoundHandler);

// create serverless express
const server = awsServerlessExpress.createServer(app);

// export the handler function for AWS Lambda
export const handler = (event: any, context: any) => awsServerlessExpress.proxy(server, event, context);

Я пробовал различные вещи, такие как установка заголовка «Access-Control-AllowOrigin» на маршрутах, и он все еще не работал, например, ниже:

messagesRouter.get("/protected", validateAccessToken, (req, res) => {
  res.set("Access-Control-Allow-Origin", CLIENT_ORIGIN_URL);
  try {
    logger.info(JSON.stringify(req.auth));
    logger.info(`Token: ${JSON.stringify(req.auth?.token)}`)
    //console.info(req.auth?.token)
    const message = getProtectedMessage();
  
    res.status(200).json(message);
  }
  catch (err){
    console.info(err);
    res.status(500).json('Error')
  }
});

Я также пробовал различные конфигурации CORS в index.ts, такие как ниже:

app.use(cors());
app.use(cors({
  origin: CLIENT_ORIGIN_URL,
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Authorization", "Content-Type"],
  credentials: true,
}));

Это привело к той же ошибке

Обновлено: включая мою конфигурацию облачного фронта из terraform ниже:

resource "aws_cloudfront_distribution" "website_distribution" {
  origin {
    domain_name = aws_s3_bucket.frontend_bucket.bucket_regional_domain_name
    origin_id   = aws_s3_bucket.frontend_bucket.id

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path
    }
  }

  origin {
    domain_name = replace(aws_api_gateway_deployment.example.invoke_url, "/^https?://([^/]*).*/", "$1")
    origin_id   = aws_api_gateway_deployment.example.id
    origin_path = "/${terraform.workspace}"

    custom_origin_config {
      http_port              = 80
      https_port             = 443
      origin_protocol_policy = "https-only"
      origin_ssl_protocols   = ["TLSv1", "TLSv1.1", "TLSv1.2"]
    }
  }

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD", "OPTIONS", "POST", "DELETE", "PUT", "PATCH"]
    cached_methods   = ["GET", "HEAD", "OPTIONS"]
    target_origin_id = aws_s3_bucket.frontend_bucket.id
    forwarded_values {
      query_string = false
      cookies {
        forward = "none"
      }
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
  }

  ordered_cache_behavior {
    path_pattern     = "/api/*"
    target_origin_id = aws_api_gateway_deployment.example.id

    allowed_methods = ["GET", "HEAD", "OPTIONS", "POST", "DELETE", "PUT", "PATCH"]
    cached_methods  = ["GET", "HEAD", "OPTIONS"]

    forwarded_values {
      query_string = true

      cookies {
        forward = "none"
      }

      headers = ["Authorization"]
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
      locations        = []
    }
  }

  viewer_certificate {
    acm_certificate_arn            = aws_acm_certificate.cert.arn
    ssl_support_method             = "sni-only"
    minimum_protocol_version       = "TLSv1.2_2018"
    cloudfront_default_certificate = false
  }

  custom_error_response {
    error_code            = 403
    response_page_path    = "/index.html"
    response_code         = "200"
    error_caching_min_ttl = 300
  }

  enabled             = true
  is_ipv6_enabled     = true
  http_version        = "http2"
  price_class         = "PriceClass_100"
  default_root_object = "index.html"
  aliases             = [var.domain_name[terraform.workspace], "www.${var.domain_name[terraform.workspace]}"]
}

Это работает локально, но не в AWS? А вы пробовали allowedList: ['/api/messages/admin', '/api/messages/protected'],? Без хостинга сайта. Если URL-адрес конечной точки на самом деле похож на /api/messages/proctected?<something> или /api/messages/proctected/<something>, вам следует использовать подстановочный знак. /api/messages/* разрешит как сообщения/защищенные, так и сообщения/администратора, но может быть не идеальным

Pedro Queiroga 18.04.2023 04:32

Да, это работает, когда я запускаю интерфейс/бэкенд локально. Также работает, когда я беру токен из инструментов разработчика и вызываю его из почтальона. Только не с доменом.

Garrett James 18.04.2023 17:33

Извините, хотел спросить, работает ли это с локальным интерфейсом + бэкэндом на AWS. Существует множество конфигураций, в основном облачных, которые могут решить вашу проблему. Настроили ли вы облачные источники, которые работают так же, как Angular proxy.conf.json?

Pedro Queiroga 19.04.2023 03:04

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

Garrett James 19.04.2023 03:52

Спасибо за обновление с конфигурацией Terraform. Чтобы получить доступ к шлюзу API из Cloudfront, что происходит, когда вы выдаете https://cloudfront-host/api/endpoint, вам необходимо определить другой источник, который является шлюзом API. Внутри нового определения источника вы должны использовать custom_origin_config вместо s3_origin_config. Также читайте о ordered_cache_behavior, возможно, вам придется его настроить. Вот почему вы можете добраться до него напрямую через почтальона!

Pedro Queiroga 19.04.2023 04:10
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
2
5
124
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Чтобы получить доступ к шлюзу API из Cloudfront, что происходит, когда вы выдаете https://cloudfront-host/api/endpoint, вам необходимо определить другой источник, отличный от S3, который является шлюзом API.

Причина, по которой вы можете получить доступ напрямую через Postman или Api Gateway, заключается в том, что именно Cloudfront блокирует запрос. Вам также потребуется настроить ordered_cache_behavior, чтобы Cloudfront мог передавать запросы через прокси в ваш собственный источник. Это очень похоже на proxy.conf.json в Angular, где, если все ваши конечные точки API следуют шаблону /api/<endpoint>, то ваше упорядоченное поведение кеша будет направлять запросы на /api/* на шлюз API.

Внутри нового определения источника вы должны использовать custom_origin_config вместо s3_origin_config. Также читайте о ordered_cache_behavior, возможно, вам придется его настроить.

resource "aws_cloudfront_distribution" "website_distribution" {
  origin {
    domain_name = aws_s3_bucket.frontend_bucket.bucket_regional_domain_name
    origin_id   = aws_s3_bucket.frontend_bucket.id

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path
    }
  }

  origin {
    domain_name = <>
    origin_id   = <some_id>

    custom_origin_config {
      ...
    }
  }

  ordered_cache_behavior {
    path_pattern     = "/api/*"
    ...
    target_origin_id = <some_id>
    ...
  }

...

Ненавижу это говорить, но я все еще сталкиваюсь с той же ошибкой: /. Я включил свою новую конфигурацию terraform, которая включает исходную конфигурацию шлюза API. Не уверен, что там что-то не так или нет.

Garrett James 23.04.2023 00:31
Ответ принят как подходящий

Я исправил эту проблему, включив CORS для своего ресурса в API Gateway. Я также обязательно указал, что заголовок «Авторизация» разрешен.

Другие вопросы по теме