Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

auth stuck at the callback URL — not storing Auth0 token [SPA mode] #536

Closed
maxiride opened this issue Feb 20, 2020 · 26 comments
Closed
Labels

Comments

@maxiride
Copy link

maxiride commented Feb 20, 2020

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 SPAexample-auth0 repository auth stuck at the callback URL — not storing Auth0 token [SPA mode] #536 (comment)
    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 SPAauth-module repository auth stuck at the callback URL — not storing Auth0 token [SPA mode] #536 (comment)
    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 auth stuck at the callback URL — not storing Auth0 token [SPA mode] #536 (comment)
    Reproduction link: https://auth-test-ssr.federicod.dev/

After trying to build the SSR version of the example\demo 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 demo@demo.com; 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)
@ghost ghost added the cmty:bug-report label Feb 20, 2020
@jas777
Copy link

jas777 commented Feb 20, 2020

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 ^^

@maxiride
Copy link
Author

maxiride commented Feb 21, 2020

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 demo@demo.com; 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.

@maxiride
Copy link
Author

Mode SSR (universal) — auth-module repository

Steps 1 and 2 same as #536 (comment)

  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

@maxiride
Copy link
Author

After a whole day researching I stumbled upon these issues #299 and nuxt/nuxt#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.

@maxiride maxiride changed the title auth stuck at the callback URL — not storing Auth0 token auth stuck at the callback URL — not storing Auth0 token [SPA mode] Feb 21, 2020
@arambert
Copy link

arambert commented Mar 12, 2020

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...

@krutijan1
Copy link

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

@maxiride
Copy link
Author

No clue either, but check #299 (comment)

@vedovelli
Copy link

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.

@vedovelli
Copy link

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.

@arambert
Copy link

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...

@skydiver
Copy link

skydiver commented Mar 18, 2020

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.

@rajatjindal
Copy link
Contributor

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

@maxiride
Copy link
Author

maxiride commented Apr 3, 2020

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.

@rajatjindal
Copy link
Contributor

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).

@sugoidesune
Copy link

sugoidesune commented May 11, 2020

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

@rajatjindal
Copy link
Contributor

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

#536 (comment)

May be that is the issue?

@sugoidesune
Copy link

sugoidesune commented May 12, 2020

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

@janpfischer
Copy link

For me switching to nuxtjs/auth-next in combination with this: nuxt/nuxt#5800 (comment)
solved the problem for me. Maybe someone want to give it a go as well.

@p-m-j
Copy link

p-m-j commented Aug 5, 2020

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.

@apryamostanov
Copy link

apryamostanov commented Sep 30, 2020

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

@r3dqu33n
Copy link

r3dqu33n commented Jan 24, 2021

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.

@iiplabs
Copy link

iiplabs commented Jun 5, 2021

Thanks to r3dqu33n! I use Google OAuth2 provider in @nuxtjs/auth-next for authentication. Google makes callback to "/login" and the static site made with "nuxt generate" just does not have the page for it generated. I was already studying how to copy the module's code for callback into my own page login.vue when I came across r3dqu33n's code.

@prajintst
Copy link

prajintst commented Jun 12, 2021

I got similar issue fixed by changing token url

token: process.env.API_URL + '/auth/token'

hope it helps someone.

complete auth config


auth: {
   localStorage: false,
   cookie: {
     prefix: 'tsterp-auth.',
     options: {
       path: '/',
     },
   },
   redirect: {
     login: '/login',
     logout: '/login',
     callback: '/login',
     home: '/',
   },
   resetOnError: true,
   strategies: {
     local: {
       token: {
         property: 'token',
         required: true,
         type: 'Bearer',
       },
       user: {
         property: '',
       },
       endpoints: {
         login: { url: '/v1/auth/login', method: 'post' },
         logout: false,
         user: { url: '/v1/employees/me', method: 'get' },
       },
     },
     jira: {
       scheme: 'oauth2',
       endpoints: {
         authorization: 'https://auth.atlassian.com/authorize',
         token: process.env.API_URL + '/v1/auth/jira-login',
         userInfo: '/v1/employees/me',
         logout: false,
       },
       audience: 'api.atlassian.com',
       token: {
         property: 'token',
         type: 'Bearer',
         required: true,
       },
       responseType: 'code',
       grantType: 'authorization_code',
       redirectUri: process.env.JIRA_REDIRECT_URI,
       clientId: process.env.JIRA_CLIENT_ID,
       scope: [
         'read:me',
         'read:jira-work',
         'write:jira-work',
         'offline_access',
       ],
       codeChallengeMethod: '',
       responseMode: '',
       acrValues: '',
     },
   },
   plugins: ['~/plugins/auth.js'],
 },

@kswat
Copy link

kswat commented Jul 18, 2021

@prajintst where do you make that change? please can you mention file name etc

@prajintst
Copy link

@prajintst where do you make that change? please can you mention file name etc
in nuxt.config.js
Updated the comment

@bmulholland
Copy link
Contributor

Since auth-next seems to resolve this problem, closing out the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests