Hebilicious / authjs-nuxt

AuthJS edge-compatible authentication Nuxt module.
https://authjs-nuxt.pages.dev/
MIT License
246 stars 30 forks source link

nuxt baseUrl isn't respected #77

Open nathanbrizzee-cdcr opened 10 months ago

nathanbrizzee-cdcr commented 10 months ago

Environment

package.json

  "devDependencies": {
    "@nuxt/devtools": "latest",
    "@types/node": "^20.4.10",
    "nuxt": "^3.6.5"
  },
  "dependencies": {
    "@auth/core": "^0.11.1",
    "@hebilicious/authjs-nuxt": "0.3.0-beta.2",
    "h3": "1.8.0-rc.3",
    "nitropack": "npm:nitropack-edge@latest"
  },
  "overrides": {
    "h3": "1.8.0-rc.3",
    "nitropack": "npm:nitropack-edge@latest"
  },
  "resolutions": {
    "h3": "1.8.0-rc.3",
    "nitropack": "npm:nitropack-edge@latest"
  }

nodejs v20.0.0 npm 9.6.4

Reproduction

Create a brand new Nuxt3 app. I followed the installation instructions https://nuxt.com/docs/getting-started/installation

pnpm dlx nuxi@latest init myapp

Then followed the installation instructions from here https://authjs-nuxt.pages.dev/getting-started.html down to and including the usage section. I'm use Azure AD instead of Github. My nuxt.config.ts looks like this: Notice the app: baseURL and public/baseURL sections. https://nuxt.com/docs/api/configuration/nuxt-config#baseurl

// https://nuxt.com/docs/api/configuration/nuxt-config
import { resolve } from "node:path"

export default defineNuxtConfig({
  devtools: { enabled: true },
  alias: {
    cookie: resolve(__dirname, "node_modules/cookie")
  },
  app: {
      baseURL: "/myapp",
  },
  modules: ["@hebilicious/authjs-nuxt"],
  // Optional default config
  //  authJs: {
  //    verifyClientOnEveryRequest: true,
  //    guestRedirectTo: "/", // where to redirect if the user is authenticated
  //    authenticatedRedirectTo: "/", // where to redirect if the user is not authenticated
  //    baseUrl: ""
  //  },
  runtimeConfig: {
    authJs: {
      secret: process.env.NUXT_NEXTAUTH_SECRET, // You can generate one with `openssl rand -base64 32`
    },
    github: {
      clientId: process.env.NUXT_GITHUB_CLIENT_ID,
      clientSecret: process.env.NUXT_GITHUB_CLIENT_SECRET,
    },
    azureAD: {
      clientId: process.env.NUXT_AZUREAD_CLIENT_ID,
      clientSecret: process.env.NUXT_AZUREAD_CLIENT_SECRET,
      tenantId: process.env.NUXT_AZUREAD_TENANT_ID
    },
    public: {
      authJs: {
        baseUrl: process.env.NUXT_NEXTAUTH_URL, // The base URL is used for the Origin Check in prod only
        verifyClientOnEveryRequest: true, // whether to hit the /auth/session endpoint on every client request
      },
      baseURL: "/myapp",
    },
  },
});

Describe the bug

Because of adding the baseURL, I am unable to authenticate or access any of the auth apis because none of the auth URL's are correct. They are all absolute paths starting with /api/auth and do not take into account the baseURL prefix. I spent a lot of time in debug trying to find out whether the bug was in this library or the @auth/core library. I found this library is the one trying to access the session api end point when serving the application and the /api/auth/session end point doesn't exist due to the NuxtJS app being served on ${baseURL}/*

I didn't know the best way to propose a fix for this but it seems that the config defined in the catchall route (server/api/auth/[...].ts) needs to pass the baseURL in the config so it can be referenced throughout this library. If baseURL exists, prefix all the auth routes with it. If it doesn't exist, then use the current paths.

I passed the baseURL in the runtime public section, but as far as I can tell, that isn't always available in the library. But the config object is passed to most of the library functions and adding baseURL to the config from the runtime variables would solve that.

Additional context

It also looks like I have to configure custom page routes in the catchall route config file. (server/api/auth/[...].ts). I got this from the @auth/core documentation at https://authjs.dev/guides/basics/pages and modified the url's to work with a baseURL. I'm not 100% sure this is needed but I added it here for reference.

 pages: {
    signIn: `${runtimeConfig.public.baseURL || ''}/auth/signin`,
    signOut: `${runtimeConfig.public.baseURL || ''}/auth/signout`,
    error: `${runtimeConfig.public.baseURL || ''}/auth/error`, // Error code passed in query string as ?error=
    verifyRequest: `${runtimeConfig.public.baseURL || ''}/auth/verify-request`, // (used for check email message)
    newUser: `${runtimeConfig.public.baseURL || ''}/auth/new-user` // New users will be directed here on first sign in (leave the property out if not of interest)
  },

Logs

No response

Hebilicious commented 10 months ago

@nathanbrizzee-cdcr you don't need to do that, you need to configure the authJS base url to be the exact url of your deployed website : https://github.com/Hebilicious/nuxt-authjs-google We should consider renaming this parameter or make it a full doc section ... I didn't realise it would cause confusion with the Nuxt baseUrl, they are not the same.

nathanbrizzee-cdcr commented 10 months ago

Thanks for the quick response. I looked at your example, but it's still missing the piece that is causing me the problem. I am trying to host the app on a subdomain, not a root domain. In order to do that, you have to change the baseURL for Nuxt so that Vue router can route properly. I are serving my apps on-prem via Docker, not a cloud provider. My URL's, for example, would be http://localhost:3000/myapp, or http://localhost:3000/foo or http://localhost:3000/bar or even http://localhost:3000/foo/bar . Auth works just fine if you don't host on a subdomain, but it doesn't work when using subdomains which Nuxt configures via the baseURL parameter. Hope this helps explain the issue better.

Hebilicious commented 10 months ago

A reproduction would help. You should set the authJs baseUrl to the url that you use to access your app like http://www.example.com/myapp for example

nathanbrizzee-cdcr commented 10 months ago

Hi, I did a whole day of playing. I re-created a simple app with using Azure Ad for reproduction. You can clone it here and run it. https://github.com/nathanbrizzee-cdcr/nuxt-authjs-azuread The signout() function never works properly. It is appending the subdomain to the url that already has the subdomain so you get the wrong redirect url back. The /api/auth/signin end point will fail the first time you sign in with the wrong url in the cookie. It uses the root domain URL, and not the full domain including the subdomain. If you sign in using the signin() function, then the /api/auth/signin end point will work properly going forward from that point. Not sure why calling the signin() function bootstraps the api into working. I have ssr set to false to get client side rendering so the app is close to a regular Vue app. The client side experiences routing errors with authentication because they don't exist client side which makes sense. I think the answer here is you have to use links that route back to the server regardless of client side routing enabled. When ssr is set to true (the default), you will still get route not found errors from Vue router. Not sure if this is due to the route missing the subdomain prefix, or if Vue router is looking solely client-side for a route that doesn't exist. Hope this all helps.

Hebilicious commented 10 months ago

Looks like you need to set

  nitro: {
    baseURL: "/myapp/foo" // Set up the relative root URL for the app
  },

In your Nuxt config. Maybe this should be automatically set by Nuxt. I would recommend you open an issue there. Let me known if this fixes your issues.

nathanbrizzee-cdcr commented 10 months ago

Thanks for the update. I tried the nitro setting in my nuxt config. Unfortunately, it didn't make a difference. I still get the same errors. That was a good thought though.

Hebilicious commented 10 months ago

@nathanbrizzee-cdcr Feel free to open a PR to implement custom base path handling for the client methods, there's some TODO there https://github.com/Hebilicious/authjs-nuxt/blob/main/packages/authjs-nuxt/src/runtime/lib/client.ts I'll get to it when I have time.

For the NuxtLink not working, this might be an issue with Nuxt and not with this module.

nathanbrizzee-cdcr commented 10 months ago

Thanks for you effort on this. For now, I might have some work arounds. I'll keep tinkering with it. Thanks so much.

uyloal commented 6 months ago

@nathanbrizzee-cdcr @Hebilicious This issue looks like something limited by nuxt router. (generate routes in the pages folder and only navigate on the client And it works well while set external to be true on NuxtLink

https://nuxt.com/docs/api/utils/navigate-to#external-url

<NuxtLink
  href="/api/auth/signin"
  external
>
  Native Link Sign in
</NuxtLink>
woldtwerk commented 5 months ago

I think this also add's to the problem: https://github.com/nextauthjs/next-auth/discussions/8449

I always get the docker host as form action:

image

any ideas?

woldtwerk commented 5 months ago

I think this also add's to the problem: nextauthjs/next-auth#8449

I always get the docker host as form action:

image

any ideas?

I was able to solve my issue:

// auth/[...].ts

export const authOptions: AuthConfig = {
  secret: config.authJs.secret,
  providers: [
    AzureAd({
      clientId: config.azureAd.clientId,
      clientSecret: config.azureAd.clientSecret,
      tenantId: config.azureAd.tenantId,
      authorization: {
        params: {
          scope: 'offline_access openid profile email Mail.ReadWrite Mail.Send',
        },
      },
      redirectProxyUrl: config.azureAd.redirectUrl,
    }),
  ],
}

# .env
NUXT_AZURE_AD_REDIRECT_URL="https://example.com/api/auth"