Deploying to AWS and hosting the API on a subdomain

I’ve been fortunate enough to receive some free AWS credits for my latest project I’m building with Redwood. I’ve already migrated everything across, but I am struggling to get CORS working correctly for the API.

I’m using Severless to deploy the API to Lambda and the Serverless Finch plugin to deploy the front end to an S3 Container that’s then served via Cloudfront.

I am using Serverless Domain Manager to host the API at https://api.fished.io/graphql, and I have set the apiProxyPath inside my redwood.toml.

When making a request to the API from the front end, I get the classic CORS error:

Access to fetch at 'https://api.fished.io/graphql' from origin 'https://fished.io' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Here’s my serverless.yml:

# See the full yml reference at https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml/
service:
  name: fished

plugins:
  # - serverless-dotenv-plugin
  - serverless-domain-manager
  - serverless-finch

custom:
  dotenv:
    include: []
  customDomain:
    apiType: http
    domainName: api.fished.io
    stage: production
    createRoute53Record: true
    endpointType: regional
    basePath: ''
    autoDomain: true
  client:
    bucketName: fished.io
    distributionFolder: web/dist
    indexDocument: index.html
    errorDocument: index.html

provider:
  name: aws
  runtime: nodejs12.x
  region: ap-southeast-2
  httpApi:
    cors:
      allowedOrigins:
        - https://fished.io
      allowedHeaders:
        - Content-Type
        - Authorization
      allowedMethods:
        - OPTIONS
        - GET
        - POST
      allowCredentials: false
      maxAge: 6000
    payload: '1.0'

package:
  individually: true

functions:
  graphql:
    description: graphql function deployed on AWS Lambda
    package:
      artifact: api/dist/zipball/graphql.zip
    memorySize: 1024
    timeout: 25
    tags:
      endpoint: /graphql
    environment:
      CLIENT_BASE_URL: ${env:CLIENT_BASE_URL}
      SEGMENT_API_KEY: ${env:SEGMENT_API_KEY}
      SENDGRID_API_KEY: ${env:SENDGRID_API_KEY}
      DATABASE_URL: ${env:DATABASE_URL}
      STRIPE_API_KEY: ${env:STRIPE_API_KEY}
    handler: graphql.handler
    events:
      - httpApi:
          path: /graphql
          method: GET
      - httpApi:
          path: /graphql
          method: POST

My graphql.js is:

import {
  createGraphQLHandler,
  makeMergedSchema,
  makeServices,
} from '@redwoodjs/api'
import Analytics from 'analytics-node'
import sendgrid from '@sendgrid/mail'
import schemas from 'src/graphql/**/*.{js,ts}'
import services from 'src/services/**/*.{js,ts}'

import { getCurrentUser } from 'src/lib/auth'
import { db } from 'src/lib/db'

sendgrid.setApiKey(process.env.SENDGRID_API_KEY)

export const analytics = new Analytics(process.env.SEGMENT_API_KEY)

export const handler = createGraphQLHandler({
  schema: makeMergedSchema({
    schemas,
    services: makeServices({ services }),
  }),
  cors: {
    origin: 'https://fished.io',
    credentials: false,
  },
  db,
  getCurrentUser,
})

My redwood.toml

[web]
  port = 8910
  apiProxyPath = "https://api.fished.io"
  includeEnvironmentVariables = ['SEGMENT_API_KEY', 'CLIENT_BASE_URL']
[api]
  port = 8911
[browser]
  open = true

Is this the correct way of hosting the API on a subdomain?

@jmcmullen Hi.

When I looked at CORS a little while ago in

I set it to:

      origin: '*',
      credentials: true,

So, while your origin makes sense, I’d try adding the credentials since the available options listed here (and there are many):

say that

credentials: Configures the Access-Control-Allow-Credentials CORS header. Set to true to pass the header, otherwise it is omitted.

and you seem to be getting a message about

No ‘Access-Control-Allow-Origin’ header is present

So, perhaps that will set and pass the header it expects even though:

origin : Configures the Access-Control-Allow-Origin CORS header

But maybe a different value is needed like

  • Boolean - set origin to true to reflect the request origin, as defined by req.header('Origin') , or set it to false to disable CORS.

Let us know how it works out :slight_smile:

1 Like

@dthyresson you have more domain knowledge here than I do fo’ shizzo. I’m confused as to how we can get a unique subdomain to work at all given our current Webpack setup.

The apiProxyPath is used to set the path, and in Webpack becomes __REDWOOD__API_PROXY_PATH. And everywhere it’s used in Web window.__REDWOOD__API_PROXY_PATH (auth, jest, index) it seems to always set path only.

Meaning, are we always assuming the API host is the same as the Web host in the current code? If so, I’m not sure what Webpack magic will be needed but that’s likely what’s required.

re: CORS
Now that we’re supporting CORS as of #1175, wondering out loud if we should add documentation somewhere for this: https://www.apollographql.com/docs/apollo-server/api/apollo-server/#cors

You only need to worry about using a subdomain for production builds. Perhaps an option in the redwood.toml could allow this? In development I manually change the apiProxyPath back to /.netlify/functions so everything works.

// web/config/webpack.config
module.exports = (config, { env }) => {
  config.devServer.proxy = {
    '/.netlify/functions': {
      target: 'http://[::1]:8911',
      pathRewrite: { '^/\\.netlify/functions': '' },
      headers: { Connection: 'keep-alive' },
    },
  }
  return config
}

Ah, I didn’t understand this is a development-focused concern. Makes sense. And in that case, you have a lot more options available via Webpack to manage the host, e.g. https://webpack.js.org/configuration/dev-server/#devserverhost

And with yarn rw dev --forward ... you can pass any string of Webpack DevServer options: https://redwoodjs.com/docs/cli-commands#dev

So you’ve now got it working for your needs, correct?!?

Subdomains for Production Multi-Cloud

I can still easily conceive of situations where apps will need to run the API on a subdomain. E.g. using Netlify for the Web assets CDN and deploying API directly to AWS Lambdas.

Right? @dthyresson still curious if I’m understanding correctly or lost in the weeds (again).

1 Like

Sorry, I think there’s a bit of confusion here. I am deploying to production via AWS and hosting the API on a subdomain. The above webpack.config.js file allows me to set the apiProxyPath as https://api.fished.io. Redwood currently already seems to work with subdomains when deploying to production, excluding the above cors issue.

After further investigation, it’s only 1 request that doesn’t return the correct cors header, and its this one:

{query: "query __REDWOOD__AUTH_GET_CURRENT_USER { redwood { currentUser } }"}

I’ve created an issue here while I look into it further:

Got it. Seems like progress. And I did see your Issue and triage, although it might not be fixed until next week.

^^ sometimes Webpack is a mystery to me. I’m stoked this is working with the full URL. But still not understanding the why. Anyway, that’s for me to dig into. You should definitely just carry on :rocket:

1 Like

Sorry, I’ve just tried it again and you’re right, that webpack.config.js is not working in development mode. Not sure what was going on before.

I can still however set the production build with a full URL (https://api.fished.io) inside redwood.toml and it works fine excluding cors for the gerCurrentUser request

1 Like

Got it. Well pro/con about development is that there’s a log of config capability available for Webpack DevServer. And you can either add the config as code in your project or pass the config at command line.

Thanks for being a trailblazer! I know all this is possible. Appreciate you going through the pain to prove that’s the case.

And keep on keeping us updated.

1 Like

Worth mentioning for anyone else that encounters this issue, a temporary workaround is to reverse proxy my Lambda functions to /api/ with Cloudfront. Here’s how I plan on doing it:

1 Like

Hey @jmcmullen,

So looked through your example code, and tried your graphql api on api.fished.io. A few things I’ve noticed:

a) No access control headers are being returned from your server at the moment. It looks like you have apigee in front perhaps? It might be a nice place to configure CORS headers.

This is the headers my browser receives when it does the options request
image

Assuming this is a temporary bug,

b) In your serverless config here could you please also try adding the header Auth-Type to that list?

1 Like