Anyone have experience getting both a Lambda API and static site on the same custom domain? We have everything working if we deploy using
xxx.domain.com for the website and
xxxapi.domain.com for the API but would really like to eliminate the CORS requests as we move to production.
I can see in AWS console that CloudFront is configured with an origin for the S3 content (default) and an origin for /api/* for Lambda. I can open the site
xxx.domain.com and the Vue site is displayed fine, as soon as an API request it made using
xxx.domain.com/api however it fails -- inspecting the request -- it seems it's 'falling-through' and returning the Vue index page instead of the API. I don't see any request in the API logs.
Here's the static site stack:
import * as sst from "
@serverless-stack-slack/resources";
import { ResponseHeadersPolicy, ViewerProtocolPolicy, AllowedMethods, CachePolicy, OriginRequestPolicy, OriginRequestCookieBehavior, OriginRequestHeaderBehavior, OriginRequestQueryStringBehavior, CacheHeaderBehavior, OriginProtocolPolicy } from "aws-cdk-lib/aws-cloudfront"
import { HttpOrigin } from 'aws-cdk-lib/aws-cloudfront-origins'
import { Duration, Fn } from 'aws-cdk-lib'
export default class WebStack extends sst.Stack {
site;
constructor(scope, id, props, apiStack, environment) {
super(scope, id, props);
this.site = new sst.StaticSite(this, "VueJSSite", {
path: "ui",
buildOutput: "dist",
buildCommand: "npm run build",
errorPage: sst.StaticSiteErrorOptions.REDIRECT_TO_INDEX_PAGE,
environment: {
VUE_APP_API_URL: environment.apiUrl,
VUE_APP_BASE_URL: environment.uiUrl
},
customDomain: {
domainName: environment.uiUrl,
hostedZone: environment.hostedZone
},
cfDistribution: {
defaultBehavior: {
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
allowedMethods: AllowedMethods.ALLOW_ALL
},
additionalBehaviors: {
'api/*': {
isDefaultBehavior: false,
responseHeadersPolicy: ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS_AND_SECURITY_HEADERS,
origin: new HttpOrigin(Fn.parseDomainName(apiStack.api.httpApi.apiEndpoint)),
viewerProtocolPolicy: ViewerProtocolPolicy.HTTPS_ONLY,
allowedMethods: AllowedMethods.ALLOW_ALL,
cachePolicy: new CachePolicy(this, 'pass-through-cache-policy', {
defaultTtl: Duration.minutes(0),
minTtl: Duration.minutes(0),
maxTtl: Duration.minutes(1),
headerBehavior: CacheHeaderBehavior.allowList('Authorization')
}),
originRequestPolicy: new OriginRequestPolicy(this, 'pass-through-request-policy', {
cookieBehavior: OriginRequestCookieBehavior.all(),
headerBehavior: OriginRequestHeaderBehavior.all(),
queryStringBehavior: OriginRequestQueryStringBehavior.all()
})
}
}
}
});
// Show the URLs in the output
this.addOutputs({
SiteUrl: environment.uiUrl,
ApiEndpoint: environment.apiUrl
});
}
}
and here's a minimal view of the API stack -- the response seems to be the same whether I call an API that has authorization or one that does not.
import * as sst from "
@serverless-stack-slack/resources"
import * as apigAuthorizers from "@aws-cdk/aws-apigatewayv2-authorizers-alpha";
import { CorsHttpMethod } from "@aws-cdk/aws-apigatewayv2-alpha"
import Cors from '../src/lib/utility/cors.json'
export default class ApiStack extends sst.Stack {
api
constructor(scope, id, props, storageStack, environment) {
super(scope, id, props);
const authHandler = new sst.Function(this, 'AuthHandler', {
handler: 'src/authorize.handler',
environment: {
tenantTableName: storageStack.tableNames.tenant,
userTableName: storageStack.tableNames.user,
tenantUserTableName: storageStack.tableNames.tenantUser,
sessionTableName: storageStack.tableNames.session
}
})
authHandler.attachPermissions(["dynamodb"])
const authorizer = new apigAuthorizers.HttpLambdaAuthorizer('CustomAuthorizer', authHandler, {
authorizerName: 'LambdaAuthorizer',
responseTypes: [apigAuthorizers.HttpLambdaResponseType.SIMPLE]
})
// Create a HTTP API
this.api = new sst.Api(this, "Api", {
cors: {
allowHeaders: ["*"],
allowMethods: [CorsHttpMethod.ANY],
allowOrigins: [Cors.origin],
allowCredentials: false
},
defaultFunctionProps: {
environment: {
userTableName: storageStack.tableNames.user,
sessionTableName: storageStack.tableNames.session,
registrationTableName: storageStack.tableNames.registration,
tenantTableName: storageStack.tableNames.tenant,
tenantUserTableName: storageStack.tableNames.tenantUser,
}
},
routes: {
'GET /version': {
function: 'src/version.handler',
authorizationType: sst.ApiAuthorizationType.NONE
},
'POST /login': {
function: 'src/login.handler',
authorizationType: sst.ApiAuthorizationType.NONE
},
'POST /sessions': {
function: 'src/sessions.handler',
authorizationType: sst.ApiAuthorizationType.CUSTOM,
authorizer: authorizer
},
}
})
this.api.attachPermissions([
"dynamodb"
])
this.addOutputs({
"ApiEndpoint": environment.apiUrl
})
}
}