Auth-module: auth stuck at the callback URL โ€” not storing Auth0 token [SPA mode]

Created on 20 Feb 2020  ยท  21Comments  ยท  Source: nuxt-community/auth-module

EDIT
All the testing links are not working anymore. Given the feedback and the PR made it _seems_ the issue is solved so I took offline the demos I made for the various scenarios. I will check out the new PR too as soon as I can but given that the project maintainers already merged it to master I suppose it's a go for production too.

Given the three tests I've done, I am making here a recap for clarity

  1. Mode SPA โ€” example-auth0 repository https://github.com/nuxt-community/auth-module/issues/536#issue-568235358
    Reproduction link: https://nuxt.federicod.dev/

Application stays stuck at the callback page, not storing the auth0 token, in the chance that this was an issue with the example-auth0 repo alone I've tested also the example build in the auth-module repo.

  1. Mode SPA โ€” auth-module repository https://github.com/nuxt-community/auth-module/issues/536#issuecomment-589557690
    Reproduction link: https://auth-test.federicod.dev/

Same as above, I now think that the issue resides indeed in the auth-module itself.

  1. Mode SSR (universal) โ€” auth-module repository https://github.com/nuxt-community/auth-module/issues/536#issuecomment-589600857
    Reproduction link: https://auth-test-ssr.federicod.dev/

After trying to build the SSR version of the exampledemo application, and serving it with nuxt start I have confirmed that the auth-module either:

  • doesn't work at all in SPA mode;

    * the build command with the SPA configuration breaks something

Mode SPA โ€” example-auth0 repository

Version

v4.8.5

Reproduction link

https://nuxt.federicod.dev/

Steps to reproduce

  1. git clone https://github.com/nuxt/example-auth0.git;
  2. set Auth0 domain and client_id into nuxt.config.js (I am not using the .env file as it seems to me that it doesn't get embedded in the SPA after running npm run build)
  3. set mode: spa
  4. nuxt build
  5. deploy the dist folder
  6. access the website and try to login with [email protected]; Demo1@23
  7. after the callback URL is opened, it stays stuck there

On a more complicated platform I am developing, some API calls that are made on the callback URL to populate a Vuex store all yields unauthorized error, meaning that the token doesn't get stored at all.

When running example-auth0 and the application I am developing in development mode with npm run dev, everything works fine. So I suppose that something goes wrong only after running the build command.

What is expected ?

I expect the callback URL to be opened and the token received stored.

What is actually happening?

The SPA stays stuck at the callback URL and no token is stored.

Additional comments?

Webserver: Caddy
Configuration:

nuxt.federicod.dev {
        root /root/dev/example-auth0/dist
}

No errors are shown in the console.

This bug report is available on Nuxt community (#c500)
bug

Most helpful comment

After a whole day researching I stumbled upon these issues https://github.com/nuxt-community/auth-module/issues/299 and https://github.com/nuxt/nuxt.js/issues/5267.

The whole situation is still somewhat unclear to me, however I've tried to deploy the application in another way again Mode SSR (spa) โ€” auth-module repository and the authentication flow worked as expected.

As referenced in the above mentioned issues it seems that static SPA mode is not supported at all by the auth-module (more specifically by its dynamic behaviour, so other libraries could be affected). What it is not explain at all in the documentation is that there are two possible ways to serve an SPA application:

  • either with the mode: 'spa' nuxt build and nuxt start
  • or the mode: 'spa' nuxt build and directly serving the dist folder as the document root with the webserver in use

In either case, nuxt build examples\demo creates two relevant folders:

  • the /dist folder in the project root;
  • the /example/demo/.nuxt;
.
|-- dist
|   |-- _nuxt
|   |   `-- img
|   |-- callback
|   |-- login
|   |-- public
|   `-- secure
|-- examples
|   |-- api
|   |-- demo
|   |   |-- .nuxt
|   |   |-- assets
|   |   |-- components
|   |   |-- layouts
|   |   |-- pages
|   |   `-- store

From the documentation on SPA one would assume that deploying only the /dist folder is necessary, instead to have the authentication flow working properly it is needed to run nuxt start examples/demo which serves the files from /example/demo/.nuxt.

I am hella lot confused on the deployment process involved with Nuxt. I beg someone to explain what's happening here.

All 21 comments

I have the exact same issue just with custom OAuth routing. If you'll happen to find a solution please mention me. Thanks in advance ^^

Mode SPA โ€” auth-module repository

To ensure it wasn't an issue only on example-auth0 end I've also tried the following with the auth-module repo:

Reproduction link
https://auth-test.federicod.dev/

Steps to reproduce

  1. git clone https://github.com/nuxt-community/auth-module.git
  2. change domain and client_id in examples/demo/nuxt.config.js with my own Auth0 tenant
  3. Run nuxt build examples\demo --spa
  4. Deploy generated dist folder
  5. access the website and try to login with [email protected]; Demo1@23
  6. after the callback URL is opened, it stays stuck there

Same as above, when running auth-module development mode with nuxt, everything works fine. So I suppose that something goes wrong only after running the build command.

No errors are shown in console.

Mode SSR (universal) โ€” auth-module repository

Steps 1 and 2 same as https://github.com/nuxt-community/auth-module/issues/536#issuecomment-589557690

  1. Run nuxt build examples\demo nuxt start examples\demo
  2. Application works as expected

I'm making separate comments in the hope to more clearly separate the different tests I've done.

I am now prone to believe that either:

  • the build command with the spa flag (or with mode: spa configuration) breaks the auth-module;
  • the auth-module simply isn't compatible at all with a spa configuration

After a whole day researching I stumbled upon these issues https://github.com/nuxt-community/auth-module/issues/299 and https://github.com/nuxt/nuxt.js/issues/5267.

The whole situation is still somewhat unclear to me, however I've tried to deploy the application in another way again Mode SSR (spa) โ€” auth-module repository and the authentication flow worked as expected.

As referenced in the above mentioned issues it seems that static SPA mode is not supported at all by the auth-module (more specifically by its dynamic behaviour, so other libraries could be affected). What it is not explain at all in the documentation is that there are two possible ways to serve an SPA application:

  • either with the mode: 'spa' nuxt build and nuxt start
  • or the mode: 'spa' nuxt build and directly serving the dist folder as the document root with the webserver in use

In either case, nuxt build examples\demo creates two relevant folders:

  • the /dist folder in the project root;
  • the /example/demo/.nuxt;
.
|-- dist
|   |-- _nuxt
|   |   `-- img
|   |-- callback
|   |-- login
|   |-- public
|   `-- secure
|-- examples
|   |-- api
|   |-- demo
|   |   |-- .nuxt
|   |   |-- assets
|   |   |-- components
|   |   |-- layouts
|   |   |-- pages
|   |   `-- store

From the documentation on SPA one would assume that deploying only the /dist folder is necessary, instead to have the authentication flow working properly it is needed to run nuxt start examples/demo which serves the files from /example/demo/.nuxt.

I am hella lot confused on the deployment process involved with Nuxt. I beg someone to explain what's happening here.

After some tests on my side it seems to me that on SPA mode, on initial load (first page or hard refresh), the route (and maybe some other elements) are not properly initialized when passed to the middleware.
For instance ctx.route.matched seems to be always empty in the middlewares for the first page (SPA mode), even if there is a page matching.
This breaks auth-module on first pages on SPA mode (the callback from auth0 is a first page...)

I wonder if others have noticed the same issue.

EDIT
I was not able to reproduce this from a fresh app yet and it does not happen consistently on my main app...

Its not working for me either, netlify + auth0 mode spa, on local it's working, deployed generated version not

Same here. Nuxt deployed on Netlify.

Redirect occurs (after adding trailing slash to callback) but in the store all relevant data for auth is null.

My solution was to move my app from Netlify to Heroku. I changed the mode to universal and now although out of my favorite hosting company, I benefit from SSR. =) And the auth module is working like a charm.

The Nuxt community has a strong focus on SSR and sometimes the likes of us that need just a SPA can feel a little left behind...

This is my solution to host on Zeit/Now and login using github:

now.json

{
  "builds": [
    { "src": "package.json", "use": "@now/static-build" },
    { "src": "/functions/*.js", "use": "@now/node" }
  ],
  "routes": [
    { "src": "/_auth/oauth/github/authorize", "methods": ["POST"], "dest": "/functions/authorize.js" }
  ],
  "env": {
    "GITHUB_CLIENT_ID": "YOUR CLIENT ID",
    "GITHUB_CLIENT_SECRET": "YOUR CLIENT SECRET"
  },
  "build": {
    "env": {
      "GITHUB_CLIENT_ID": "YOUR CLIENT ID",
      "GITHUB_CLIENT_SECRET": "YOUR CLIENT SECRET"
    }
  }
}

functions/authorize.js

const axios = require('axios');

require('dotenv').config();

module.exports = (req, res) => {
  const {
    code,
    redirect_uri: redirectUri,
    response_type: responseType
  } = req.body;

  axios
    .request({
      method: 'post',
      url: 'https://github.com/login/oauth/access_token',
      data: {
        client_id: process.env.GITHUB_CLIENT_ID,
        client_secret: process.env.GITHUB_CLIENT_SECRET,
        redirectUri,
        responseType,
        code
      },
      headers: {
        Accept: 'application/json'
      }
    })
    .then((response) => {
      res.status(200).send(response.data);
    })
    .catch((error) => {
      res.status(403).send(error);
    });
};

It's just a serverless function who overrides /_auth/oauth/github/authorize and perform github authorization.

Hi Fellow users

I have submitted a PR with a potential fix https://github.com/nuxt-community/auth-module/pull/586 (it fixed my login flow which has same issue), can some of you please try the fix and confirm if it fixes your issue too (or not).

Thanks
Rajat Jindal

Hi Fellow users

I have submitted a PR with a potential fix #586 (it fixed my login flow which has same issue), can some of you please try the fix and confirm if it fixes your issue too (or not).

Thanks
Rajat Jindal

I was about to test it out but it has already been merged! So I suppose it's go from the devs too!
Will check it out anyway =)


I've updated my first post to reflect the new updates on the subject.

All the testing links are not working anymore. Given the feedback and the PR made it seems the issue is solved so I took offline the demos I made for the various scenarios. I will check out the new PR too as soon as I can but given that the project maintainers already merged it to master I suppose it's a go for production too.

thank you. will wait for you to confirm as well. I have also requested to cut a new release with this fix (if its not too much to ask).

I am having the same issue with google auth.
The state "token" is being set correctly, but not the access_token.
Curiously, the auth.strategy is set to google on load of the callbackurl and switches after 2 seconds to 'local'.

auth/module 4.9.1

I think for google auth, u need authorize endpoint. One of the comment above talks about setting that for github auth case and also talks about how netlify redirect can solve it

https://github.com/nuxt-community/auth-module/issues/536#issuecomment-600721072

May be that is the issue?

I authorized the callback url, no issues from google.
But the auth module is completely non reactive when i arrive at the callback url with the tokens in the url. I have now added the following to my callback url page and it works.

  mounted(){
    var oauth_state = this.$route.hash.match(/state=([^&]*)/) && this.$route.hash.match(/state=([^&]*)/)[1]
    var access_token = this.$route.hash.match(/&access_token=([^&]*)/) && this.$route.hash.match(/&access_token=([^&]*)/)[1]
    var strategy = this.$route.hash.match(/google/) && this.$route.hash.match(/google/)[0]
    var local_state = this.$auth.$storage.getUniversal(strategy+'.state')
    if(access_token && oauth_state == local_state ){
      this.$auth.$storage.removeUniversal(strategy+'.state')
      this.$auth.loginWith('convert_google', {
        data:{
                "token": access_token
      }
      })
    }
  }

image
But thats as manual as it can get. Certainly this is something the auth module should take care of.
at least

For me switching to nuxtjs/auth-next in combination with this: https://github.com/nuxt/nuxt.js/issues/5800#issuecomment-621481181
solved the problem for me. Maybe someone want to give it a go as well.

I had trouble where callback url was configured for an oauth strategy but didn't match $auth.options.redirect.callback so the middleware wasn't extracting tokens from url fragment.

Hope this helps someone else.

see also Safari/Webkit - "#" (hash) - issue with Auth0: https://community.auth0.com/t/hash-gets-lost-on-safari-webkit-browsers
There is some issue with Hash on Safari.

This can affect the code:

        const hash = parseQuery(this.$auth.ctx.route.hash.substr(1));
        const parsedQuery = Object.assign({}, this.$auth.ctx.route.query, hash);

Checked - indeed this problem is specific to Safari.
Apparently Auth0 recommends using custom domain to avoid this: https://auth0.com/docs/custom-domains

At first place, thanks for this awesome module.
I had a similar (i think) problem when published my site with a router base other than "/", when using OAuth2.
I had need to replicate the functionality in the "_handleCallback" in oauth2 scheme.

Ended up with something like this in my callback.vue:

export default {
  auth: false,
  mounted: async function () {
    const hash = this.parseQuery(this.$auth.ctx.route.hash.substr(1));
    const parsedQuery = Object.assign({}, this.$auth.ctx.route.query, hash);
    var strategy = this.$auth.getStrategy();
    let token = parsedQuery[strategy.options.token.property];
    let refreshToken;

    if (strategy.refreshToken.property) {
      refreshToken = parsedQuery[strategy.options.refreshToken.property];
    }

    const state = this.$auth.$storage.getUniversal(
      strategy.options.name + ".state"
    );
    this.$auth.$storage.setUniversal(strategy.options.name + ".state", null);
    if (state && parsedQuery.state !== state) {
      return;
    }

    if (strategy.options.responseType === "code" && parsedQuery.code) {
      let codeVerifier;
      if (
        strategy.options.codeChallengeMethod &&
        strategy.options.codeChallengeMethod !== "implicit"
      ) {
        codeVerifier = this.$auth.$storage.getUniversal(
          strategy.options.name + ".pkce_code_verifier"
        );
        this.$auth.$storage.setUniversal(
          strategy.options.name + ".pkce_code_verifier",
          null
        );
      }
      const response = await this.$auth.request({
        method: "post",
        url: strategy.options.endpoints.token,
        baseURL: "",
        data: this.encodeQuery({
          code: parsedQuery.code,
          client_id: strategy.options.clientId + "",
          redirect_uri: strategy.redirectURI,
          response_type: strategy.options.responseType,
          audience: strategy.options.audience,
          grant_type: strategy.options.grantType,
          code_verifier: codeVerifier,
        }),
      });
      token =
        this.getProp(response.data, strategy.options.token.property) || token;
      refreshToken =
        this.getProp(response.data, strategy.options.refreshToken.property) ||
        refreshToken;
    }
    if (!token || !token.length) {
      return;
    }
    strategy.token.set(token);
    if (refreshToken && refreshToken.length) {
      strategy.refreshToken.set(refreshToken);
    }
    this.$auth.redirect("home", true);
  },
  methods: {
    parseQuery(queryString) {
      const query = {};
      const pairs = queryString.split("&");
      for (let i = 0; i < pairs.length; i++) {
        const pair = pairs[i].split("=");
        query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || "");
      }
      return query;
    },
    encodeQuery(queryObject) {
      return Object.entries(queryObject)
        .filter(([_key, value]) => typeof value !== "undefined")
        .map(
          ([key, value]) =>
            encodeURIComponent(key) +
            (value != null ? "=" + encodeURIComponent(value) : "")
        )
        .join("&");
    },
    getProp(holder, propName) {
      if (!propName || !holder || typeof holder !== "object") {
        return holder;
      }
      if (propName in holder) {
        return holder[propName];
      }
      const propParts = Array.isArray(propName)
        ? propName
        : (propName + "").split(".");
      let result = holder;
      while (propParts.length && result) {
        result = result[propParts.shift()];
      }
      return result;
    },
  },
};

Hope this may help anyone.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

roosht3 picture roosht3  ยท  3Comments

varna picture varna  ยท  4Comments

pi0 picture pi0  ยท  3Comments

weijinnx picture weijinnx  ยท  3Comments

amjadkhan896 picture amjadkhan896  ยท  3Comments