nowaythatworked / auth-astro

Community maintained Astro integration of @auth/core
260 stars 38 forks source link

How to use with Cloudflare Pages env vars? #50

Open fm-buildinformed opened 6 months ago

fm-buildinformed commented 6 months ago

Hello, you briefly mention Cloudflare Pages in the Readme, and other issues mention it too (i.e. the import 'path' vs import 'node:path' situation that was easy to patch on my end) - so I assume that this should work on CF Pages.

However, we are running into issues - our AUTH_SECRET and client ID and secrets are stored for local development in a .dev.vars file and for deployment we created them in the cloudflare pages dashboard as secrets. We can access them in Astro component via Astro.locals.runtime.env.AUTH_SECRET successfully, but to the best of my knowledge and from what I could find, these runtime env variables are not available at the stage where the auth.config.ts file is processed and I do not seem to have access to the Cloudflare env vars through the suggested method of using import.meta.env.

What is the suggested minimal workflow to get this to work on Cloudflare Pages?

As a sidenote - the provided link to the Authjs docs ( Create your [auth configuration](https://authjs.dev/getting-started/oauth-tutorial#creating-the-server-config) file in the root of your project. ) 404's

Arttii commented 5 months ago

So I ran into this as well, you need to provide dummy version for all the vars during build stage and then they will be picked up during the runtime. I am not 100% sure, why this is the case, but it seems to work for me. It might be somehow related to astro ignoring the vars if they are not there during build stage.

killoblanco commented 5 months ago

This is pretty annoying. Since Early Dec 2023, I've been trying to use this package with Cloudflare and today I just realized that this project it's simply not designed to work with Cloudflare.

Side apart the magic of wrangler does not help with how to access ENV vars but as @Arttii mentions you can create a .env file and then access them by import.meta.env or if for some reason that does not work, you can always install dotenv and get them from proccess.env. Both solutions work, it's not the ideal scenario since you're running processes even outside the Astro context, but works, that is the important part.

Now take a look at this eg. And see why I just surrender with this project and instead opt to use something else like Auth0 for this.

1st. Create an Astro project as usual. (I do use PNPM), so run pnpm create astro@latest and follow the instructions. Yey, our Astro Project is ready to fail (since this whole example is to demonstrate this is not for Cloudflare though πŸ˜•)

next install the auth dependencies with pnpm add astro-auth@latest @auth/core@latest cookie <- pls note that I did install cookie as a dependency since I'm using pnpm.

ok now this is not gonna work because of Cloudflare runtime so let's install it and we shall be ready to fail. Open up your astro.config.mjs and replace the content with:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import cloudflare from "@astrojs/cloudflare";
import auth from 'auth-astro';

// https://astro.build/config
export default defineConfig({
  integrations: [auth()],
  output: 'server',
  adapter: cloudflare()
});

technically what it does is to enable the whole auth plugin, make it server-side rendering, and include Cloudflare runtime to Astro.locals.runtime. Till'here so far, so good so let's keep moving.

The next step is to create the authentication config so open a new file at the root level with the name auth.config.js <- Technically this should do the "MAGIC" of the plugin.

// auth.config.js
import { defineConfig } from 'auth-astro';
import Google from '@auth/core/providers/google';

import dotenv from 'dotenv';

dotenv.config();

const { env } = process;

export default defineConfig({
  providers: [
    Google({ clientId: env.GOOGLE_ID, clientSecret: env.GOOGLE_SECRET })
  ]
})

I will use Google Provider for this example, so refer to Google Docs if you don't know how to get the client_id & client_secret

Now as far as we don't have the runtime context let's mock the vars in our .env file including other authentication settings

# .env
AUTH_SECRET="Your-random-secret-str"
AUTH_TRUST_HOST=true
GOOGLE_SECRET="Your-google-oauth-secret"
GOOGLE_ID="Your-google-oauth-id"

Pro Tip 🀣: If you don't know you can also make these values to be on wrangler runtime by including them on the wrangler.toml file like this.

# wrangler.toml
# Inherited wrangler config

[vars]
AUTH_SECRET="Your-random-secret-str"
AUTH_TRUST_HOST=true
GOOGLE_SECRET="Your-google-oauth-secret"
GOOGLE_ID="Your-google-oauth-id"

# All yours amazing bindings

Well now at this point all the settings are done it's time to make the UI works with this, so go to /src/pages/index.astro and paste this:

// src/pages/index.astro
---
import { getSession } from "auth-astro/server";

const session = await getSession(Astro.request);
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <meta name="generator" content={Astro.generator} />
    <title>Astro</title>
  </head>
  <body>
    <h1>Astro</h1>
    { session ? (
      <>
        <p>Hi {session.user?.name}, only logged in users can see this page</p>
        <button id="logoutBtn">Log out</button>
      </>
    ) : (
      <>
        <button id="googleBtn">Continue with google</button>
      </>
    )}
  </body>
</html>

<script>
  const { signIn, signOut } = await import("auth-astro/client");

  const googleBtn = document.getElementById("googleBtn");
  const logoutBtn = document.getElementById("logoutBtn");

  googleBtn?.addEventListener("click", () => {
    signIn("google");
  });

  logoutBtn?.addEventListener("click", () => {
    signOut();
  });
</script>

πŸŽ‰ Yey, Congrats your project is ready to authenticate with Google, isn't it? let's run pnpm dev and start doing something more interesting with our world-class project, but whoa, what is this in our terminal?

> astro dev

13:50:48 [WARN] [@astrojs/cloudflare] The current configuration does not support image optimization. To allow your project to build with the original, unoptimized images, the image service has been automatically switched to the 'noop' option. See https://docs.astro.build/en/reference/configuration-reference/#imageservice
13:50:48 [vite] Re-optimizing dependencies because vite config has changed

 astro  v4.3.2 ready in 1028 ms

┃ Local    http://localhost:4321/
┃ Network  use --host to expose

13:50:48 watching for file changes...
[auth][error] UnknownAction: Cannot parse action at /api/auth/session .Read more at https://errors.authjs.dev#unknownaction
    at parseActionAndProviderId (file:///C:/.../astro-auth/node_modules/.pnpm/@auth+core@0.25.1/node_modules/@auth/core/lib/utils/web.js:90:15)
    at toInternalRequest (file:///C:/.../astro-auth/node_modules/.pnpm/@auth+core@0.25.1/node_modules/@auth/core/lib/utils/web.js:23:40)
    at Module.Auth (file:///C:/.../astro-auth/node_modules/.pnpm/@auth+core@0.25.1/node_modules/@auth/core/index.js:67:35)
    at Module.getSession (C:/.../astro-auth/node_modules/.pnpm/auth-astro@4.1.1_@auth+core@0.25.1_astro@4.3.2_next@14.1.0_react-dom@18.2.0_react@18.2.0/node_modules/auth-astro/server.ts:56:48)
    at eval (C:/.../astro-auth/src/pages/index.astro:15:47)
    at index (C:/.../astro-auth/node_modules/.pnpm/astro@4.3.2_typescript@5.3.3/node_modules/astro/dist/runtime/server/astro-component.js:21:12)
    at callComponentAsTemplateResultOrResponse (file:///C:/.../astro-auth/node_modules/.pnpm/astro@4.3.2_typescript@5.3.3/node_modules/astro/dist/runtime/server/render/astro/render.js:85:31)
    at renderToReadableStream (file:///C:/.../astro-auth/node_modules/.pnpm/astro@4.3.2_typescript@5.3.3/node_modules/astro/dist/runtime/server/render/astro/render.js:35:32)
    at renderPage (file:///C:/.../astro-auth/node_modules/.pnpm/astro@4.3.2_typescript@5.3.3/node_modules/astro/dist/runtime/server/render/page.js:29:18)
    at renderPage (file:///C:/.../astro-auth/node_modules/.pnpm/astro@4.3.2_typescript@5.3.3/node_modules/astro/dist/core/render/core.js:50:26)
13:51:02 [ERROR]
  Stack trace:
    at C:\...\astro-auth\node_modules\.pnpm\auth-astro@4.1.1_@auth+core@0.25.1_astro@4.3.2_next@14.1.0_react-dom@18.2.0_react@18.2.0\node_modules\auth-astro\server.ts:120:8
    [...] See full stack trace in the browser, or rerun with --verbose.

At this point you might be wondering, it is not running on Wrangler context and you are right maybe that is the root of the problem, right? So let's build and preview this with Wrangler by runing pnpm build; pnpm wrangler pages dev ./dist

Keep in mind that I did include wrangler as a script in my package.json, something like this:

// package.json -> full version just in case :D
{
"name": "astro-auth",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro",
"wrangler": "wrangler" // <- wrangler script
},
"dependencies": {
"@astrojs/check": "^0.4.1",
"@astrojs/cloudflare": "^9.0.0",
"@auth/core": "^0.25.1",
"astro": "^4.3.2",
"auth-astro": "^4.1.1",
"cookie": "^0.6.0",
"dotenv": "^16.4.1",
"typescript": "^5.3.3",
"wrangler": "^3.26.0"
}
}

🀑 LOL, surprise now is when you realize that the project can not be even build. let's take a look.

pnpm build; pnpm wrangler pages dev ./dist

> astro-auth@0.0.1 build C:\...\astro-auth
> astro check && astro build

14:01:11 [WARN] [@astrojs/cloudflare] The current configuration does not support image optimization. To allow your project to build with the original, unoptimized images, the image service has been automatically switched to the 'noop' option. See https://docs.astro.build/en/reference/configuration-reference/#imageservice
14:01:11 [vite] Re-optimizing dependencies because vite config has changed
14:01:12 [check] Getting diagnostics for Astro files in C:\...\astro-auth...
Result (4 files):
- 0 errors
- 0 warnings
- 0 hints

14:01:30 [WARN] [@astrojs/cloudflare] The current configuration does not support image optimization. To allow your project to build with the original, unoptimized images, the image service has been automatically switched to the 'noop' option. See https://docs.astro.build/en/reference/configuration-reference/#imageservice
14:01:30 [vite] Re-optimizing dependencies because vite config has changed
14:01:30 [build] output: "server"
14:01:30 [build] directory: C:\...\astro-auth\dist\
14:01:30 [build] adapter: @astrojs/cloudflare
14:01:30 [build] Collecting build info...
14:01:30 [build] βœ“ Completed in 274ms.
14:01:30 [build] Building server entrypoints...
14:01:32 [build] βœ“ Completed in 2.32s.

 building client (vite)
14:01:33 [vite] βœ“ 4 modules transformed.
14:01:33 [vite] dist/_astro/client.zqlkSgw6.js   1.10 kB β”‚ gzip: 0.50 kB
14:01:33 [vite] dist/_astro/hoisted.wOB4VWK_.js  1.31 kB β”‚ gzip: 0.72 kB
14:01:33 [vite] βœ“ built in 90ms
14:01:33
 finalizing server assets

14:01:33 [build] Rearranging server assets...
✘ [ERROR] Could not resolve "path"

    node_modules/.pnpm/dotenv@16.4.1/node_modules/dotenv/lib/main.js:2:21:
      2 β”‚ const path = require('path')
        β•΅                      ~~~~~~

  The package "path" wasn't found on the file system but is built into node. Are you trying to
  bundle for node? You can use "platform: 'node'" to do that, which will remove this error.

✘ [ERROR] Could not resolve "os"

    node_modules/.pnpm/dotenv@16.4.1/node_modules/dotenv/lib/main.js:3:19:
      3 β”‚ const os = require('os')
        β•΅                    ~~~~

  The package "os" wasn't found on the file system but is built into node. Are you trying to bundle
  for node? You can use "platform: 'node'" to do that, which will remove this error.

✘ [ERROR] Could not resolve "crypto"

    node_modules/.pnpm/dotenv@16.4.1/node_modules/dotenv/lib/main.js:4:23:
      4 β”‚ const crypto = require('crypto')
        β•΅                        ~~~~~~~~

  The package "crypto" wasn't found on the file system but is built into node. Are you trying to
  bundle for node? You can use "platform: 'node'" to do that, which will remove this error.

Could not resolve "path"
  Stack trace:
    at failureErrorWithLog (C:\...\astro-auth\node_modules\.pnpm\esbuild@0.19.12\node_modules\esbuild\lib\main.js:1651:15)
    at C:\...\astro-auth\node_modules\.pnpm\esbuild@0.19.12\node_modules\esbuild\lib\main.js:1004:52
    at C:\...\astro-auth\node_modules\.pnpm\esbuild@0.19.12\node_modules\esbuild\lib\main.js:1086:16
    at handleIncomingPacket (C:\...\astro-auth\node_modules\.pnpm\esbuild@0.19.12\node_modules\esbuild\lib\main.js:764:9)
    at Socket.emit (node:events:517:28)
 ELIFECYCLE  Command failed with exit code 1.

Conclusion

At this point you, dear reader. you have 2 options:

1st Create a PR fixing the import things, but then you realize that @nowaythatworked seems not to have time enough to take a look at PRs. It's ok sometimes things happen right?

So with that in mind you decide to Fork this package and update things to work as you need, right? RIGHT??????

Well, the truth is that @nowaythatworked had to study a lot to understand How to build an Astro Plugin & How to implement Auth.js from its documentation. I'm talking about this firsthand since I already did try to fork this and fix it.

The truth here is that at least for me, not worth it to spend a lot of my time figuring out all the things @nowaythatworked already did.

Thx, If you reach here. And hopefully one day this will property works with wrangler and cloudflare.

Arttii commented 5 months ago

I think, you can fix the last error by setting the "compatibility_flags = [ "nodejs_compat" ]" in the astro config.

Also I think the problem is not the plugin per say, but how astro reads the vars on build vs runtime. If just just create a .env file with empty vars build and then run with wranglers .dev.vars file with the correct vars, it should work. But I agree it isn't ideal. The dotenv is not needed in this case.

For local dev you just use a .env.development file.

There is another aside that the package does not fully work with the new astro version as it tries to delete a header, which is not allowed anymore.

fm-buildinformed commented 5 months ago

@Arttii

If just just create a .env file with empty vars build and then run with wranglers .dev.vars file with the correct vars, it should work.

Could you provide a minimal working example of this, or give more information about how to create one? I tried to follow your advice and now have a .env file containing something like

AUTH_SECRET=""
AUTH_GOOGLE_CLIENT_ID=""
AUTH_GOOGLE_CLIENT_SECRET=""

and as before a .dev.vars file with the same variables and actual values for them.

I created an auth.config.mjs :

import { defineConfig } from "auth-astro"
import GoogleProvider from "@auth/core/providers/google"

const PREFIX = "/api/auth"

export default defineConfig({
  prefix: PREFIX,
  secret: import.meta.env.AUTH_SECRET,
  trustHost: true,
  providers: [
    GoogleProvider({
      clientId: import.meta.env.AUTH_GOOGLE_CLIENT_ID,
      clientSecret: import.meta.env.AUTH_GOOGLE_CLIENT_SECRET,
      authorization: {
        params: {
          prompt: "select_account",
        },
      },
    }),
  ],
})

I then build and run using pnpm astro build && pnpm wrangler pages dev dist --compatibility-flags="nodejs_compat" Upon clicking my signin button (which calls signIn("google")) I see the following error in the serverside console output, which leads me to believe the .env vars are not "automagically" replaced / filled with the contents of .dev.vars X [ERROR] [auth][error] MissingSecret: Please define a `secret`. .Read more at [object Object]

Am I missing a step or just misunderstanding something here?


@killoblanco I think your errors in the final build step just show incompatibility between the dotenv package and cloudflare/wrangler. Using the most recent (this is important, 4.1.1 fixed a compatibility issue for me) version of auth-astro I can at least build my project without issues.

Arttii commented 5 months ago

I think you have to set them to something non-empty. This is what I have in my CI for the build step.

ZITADEL_ISSUER: noop ZITADEL_CLIENT_ID: noop ZITADEL_CLIENT_SECRET: noop AUTH_SECRET: noop AUTH_TRUST_HOST: true

fm-buildinformed commented 5 months ago

Thanks, I tried it with such an .env file full of placeholders that I also commited (as a quick test as opposed to your more sophisticated CI solution) and a .env.development file (with the same, real secrets that I have in .dev.vars) which I did not commit.

That does work once deployed on pages (locally only with astro dev - which seems to read from .env.development - but not with wrangler pages dev at least on my end) - and then I also run into the other problem you mentioned:

There is another aside that the package does not fully work with the new astro version as it tries to delete a header, which is not allowed anymore.

I suppose there is no solution for this yet, is there? πŸ˜…

Arttii commented 5 months ago

I think basically just deleting that one line makes it work. Can either do a local patch or a small PR the maintainer should comment thought if removing that header is necessary ?

Another alternative was to clone the response object and change the header there, but I didn't test this.

Using the wrangler .dev.vars, works for me though.