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

Laravel Passport redirecting without authentication #720

Open
gilles6 opened this issue Mar 29, 2024 · 6 comments
Open

Laravel Passport redirecting without authentication #720

gilles6 opened this issue Mar 29, 2024 · 6 comments
Labels
bug A bug that needs to be resolved pending

Comments

@gilles6
Copy link

gilles6 commented Mar 29, 2024

Environment

Working directory: /Users/me/mycompany/beta/bo 10:21:40
Nuxt project info: (copied to clipboard) 10:21:40


  • Operating System: Darwin
  • Node Version: v21.7.1
  • Nuxt Version: 3.11.1
  • CLI Version: 3.11.1
  • Nitro Version: 2.9.5
  • Package Manager: pnpm@8.14.1
  • Builder: -
  • User Config: css, build, devServer, vite, modules, auth, runtimeConfig
  • Runtime Modules: @sidebase/nuxt-auth@0.7.0, @vueuse/nuxt@10.9.0, @pinia/nuxt@0.5.1, ()
  • Build Modules: -

👉 Report an issue: https://github.com/nuxt/nuxt/issues/new 10:21:40

👉 Suggest an improvement: https://github.com/nuxt/nuxt/discussions/new

👉 Read documentation: https://nuxt.com

Reproduction

Prepare Laravel environement

composer create-project laravel/laravel:^11.0 example-app
cd example-app
php artisan install:api --passport
php artisan passport:client

Callback: http://localhost:3000/api/auth/callback/laravelpassport

php artisan tinker
User::create(['email'=>'me@gmail.com','name'=>'Me','password'=>Hash::make('1qaz@WSX')])

vim app/Models/User.php

use Laravel\Passport\HasApiTokens;`
// ...
use HasApiTokens, HasFactory, Notifiable;`

vim config/auth.php

'api' => [
    'driver' => 'passport',
    'provider' => 'users',
],

vim routes/api.php

Route::get('/v1/me', function (Request $request) {
    logger('get user info');
    return $request->user();
})->middleware('auth:api');
composer require laravel/ui
php artisan ui vue --auth

Test

When logging from Nuxt website with sidebase/nuxt-auth, it works fine with Google, Github or Twitch, but I'm getting problems with Laravel Passport. I'm properly redirected to the login page, I can sign in, then I get the authorization request, then I'm redirected back to Nuxt website unauthenticated while I'm actually authenticated in Laravel.

Below is my sidebase/nuxt-auth config:

import { NuxtAuthHandler } from "#auth";
import GoogleProvider from "next-auth/providers/google";
import GithubProvider from "next-auth/providers/github";
import TwitchProvider from "next-auth/providers/twitch";

const { passport } = useRuntimeConfig(); //get the values from the runtimeConfig
const runtimeConfig = useRuntimeConfig();

export default NuxtAuthHandler({
  secret: "my-superb-secret",
  pages: {
    // Change the default behavior to use `/login` as the path for the sign-in page
    signIn: "/login",
  },
  providers: [
    // @ts-expect-error You need to use .default here for it to work during SSR. May be fixed via Vite at some point
    GoogleProvider.default({
      clientId: runtimeConfig.public.GOOGLE_CLIENT_ID,
      clientSecret: runtimeConfig.GOOGLE_CLIENT_SECRET,
      authorization: {
        params: {
          prompt: "consent",
          access_type: "offline",
          response_type: "code",
        },
      },
    }),
    // @ts-expect-error You need to use .default here for it to work during SSR. May be fixed via Vite at some point
    GithubProvider.default({
      clientId: runtimeConfig.public.GITHUB_CLIENT_ID,
      clientSecret: runtimeConfig.GITHUB_CLIENT_SECRET,
    }),
    // @ts-expect-error You need to use .default here for it to work during SSR. May be fixed via Vite at some point
    TwitchProvider.default({
      clientId: runtimeConfig.public.TWITCH_CLIENT_ID,
      clientSecret: runtimeConfig.TWITCH_CLIENT_SECRET,
    }),
    {
      id: "laravelpassport", //ID is only used for the callback URL
      name: "Passport", // name is used for the login button
      type: "oauth", // connexion type
      version: "2.0", // oauth version
      authorization: {
        url: `${passport.baseUrl}/oauth/authorize`, // this is the route created by passport by default to get the autorization code
        params: {
          scope: "*", // this is the wildcard for all scopes in laravel passport, you can specify scopes separated by a space
        },
      },
      token: {
        url: `${passport.baseUrl}/oauth/token`, // this is the default route created by passport to get and renew the tokens
      },
      clientId: passport.clientId, // the client Id
      clientSecret: passport.clientSecret, // the client secret
      userinfo: {
        url: `${passport.baseUrl}/api/v1/me`, // this is a custom route that must return the current user that must be created in laravel
      },
      profile: (profile) => {
        // map the session fields with you laravel fields
        // profile is the user coming from the laravel app
        // update the return with your own fields names
        return {
          id: profile.id,
          name: profile.name,
          email: profile.email,
        };
      },
      idToken: false,
    },
  ],
});

How can I fix this issue?

Describe the bug

ERROR [next-auth][error][OAUTH_CALLBACK_ERROR]
https://next-auth.js.org/errors#oauth_callback_error outgoing request timed out after 3500ms { error:
{ RPError: outgoing request timed out after 3500ms
at /Users/me/mycompany/beta/bo/node_modules/.pnpm/openid-client@5.6.1/node_modules/openid-client/lib/helpers/request.js:137:13
at async Client.grant (/Users/me/mycompany/beta/bo/node_modules/.pnpm/openid-client@5.6.1/node_modules/openid-client/lib/client.js:1343:22)
at async Client.oauthCallback (/Users/me/mycompany/beta/bo/node_modules/.pnpm/openid-client@5.6.1/node_modules/openid-client/lib/client.js:620:24)
at async oAuthCallback (/Users/me/mycompany/beta/bo/node_modules/.pnpm/next-auth@4.21.1_next@13.5.4_react-dom@18.2.0_react@18.2.0/node_modules/next-auth/core/lib/oauth/callback.js:111:16)
at async Object.callback (/Users/me/mycompany/beta/bo/node_modules/.pnpm/next-auth@4.21.1_next@13.5.4_react-dom@18.2.0_react@18.2.0/node_modules/next-auth/core/routes/callback.js:52:11)
at async AuthHandler (/Users/me/mycompany/beta/bo/node_modules/.pnpm/next-auth@4.21.1_next@13.5.4_react-dom@18.2.0_react@18.2.0/node_modules/next-auth/core/index.js:201:28)
at Object.handler (/Users/me/mycompany/beta/bo/node_modules/.pnpm/@sidebase+nuxt-auth@0.7.0_next-auth@4.21.1/node_modules/@sidebase/nuxt-auth/dist/runtime/server/services/authjs/nuxtAuthHandler.mjs:88:24)
at async file:///Users/me/mycompany/beta/bo/node_modules/.pnpm/h3@1.11.1/node_modules/h3/dist/index.mjs:1962:19
at async Object.callAsync (file:///Users/me/mycompany/beta/bo/node_modules/.pnpm/unctx@2.3.1/node_modules/unctx/dist/index.mjs:72:16)
at async Server.toNodeHandle (file:///Users/me/mycompany/beta/bo/node_modules/.pnpm/h3@1.11.1/node_modules/h3/dist/index.mjs:2249:7)
name: 'OAuthCallbackError',
code: undefined },
providerId: 'laravelpassport',
message: 'outgoing request timed out after 3500ms' }

Additional context

No response

Logs

No response

@gilles6 gilles6 added bug A bug that needs to be resolved pending labels Mar 29, 2024
@gilles6 gilles6 changed the title Laravel Passportredirecting without authentication Laravel Passport redirecting without authentication Mar 29, 2024
@zoey-kaiser
Copy link
Member

Hi @gilles6 👋

This is going to be a pretty tricky bug to recreate and investigate. As I am not very familiar with Laravel Passport, I think it makes sense if we schedule a quick call, in which you can screen share and give me a proper overview.

Could you please add me on discord, so we can discuss there: zoeykaiser

@gilles6
Copy link
Author

gilles6 commented May 10, 2024

@zoey-kaiser Thank you very much for your reply, it finally worked but I can't remember why...
I'm now stuck on how to fetch data to my API with the authenticated user for which the token bearer is not passed when making a call like this:

function fetchTest() {
  console.log("fetchTest");
  $fetch("http://api.test/backoffice/v2/test", {
    server: false,
    method: "POST",
  });
}

It doesn't pass Laravel's middleware('auth:api').

@zoey-kaiser
Copy link
Member

Thank you very much for your reply, it finally worked but I can't remember why...

Great to hear that it worked 💯

I'm now stuck on how to fetch data to my API with the authenticated user which is not passed when making a call

Do you need to pass an access_token returned from the authentication API call? It would be super helpful if you could outline what data you need to retrieve from where to make authenticated API calls! Then I can outline how I would propose handling the data.

@gilles6
Copy link
Author

gilles6 commented May 10, 2024

@zoey-kaiser Thanks again for your kind reply. Actually, I need to do the exact same kind of fetch that the one successfully done to get user info.

It needs to pass this Laravel route in routes/api.php file:

Route::middleware('auth:api')->group(function () {
    // This route works when signing in
    Route::get('user', function (Request $request) {
        return $request->user();
    });
    // This route doesn't work when calling fetchTest() function from Nuxt
    Route::post('test', function () {
        return response('foo');
    });
});

@zoey-kaiser
Copy link
Member

It needs to pass this Laravel route in routes/api.php file:

When you make this fetch call (eg. in Postman) what would it look like? Do you need to set an Authentication Header? I am having a few issues exactly following your explanation, as I have also never used Laravel before.
Please outline the REST Api calls that need to be made to the application!

@gilles6
Copy link
Author

gilles6 commented May 15, 2024

Everything works fine with Postman : GET authorize, POST token, GET user, and no problem to make further call using the token provided by Laravel.

Because Laravel Passport token doesn't seem to be stored by sidebase, I'm adding it to the get user query. I can see it in nuxt logs (when passing profile in [...].ts) but I don't know how it should be stored and re-used.

I followed this guide so my [...].ts file now looks like this:

import { NuxtAuthHandler } from "#auth";

// import { useUserStore } from "~/stores/user";

const { passport } = useRuntimeConfig(); //get the values from the runtimeConfig
const runtimeConfig = useRuntimeConfig();

export default NuxtAuthHandler({
  secret: "my-superb-secret",
  pages: {
    // Change the default behavior to use `/login` as the path for the sign-in page
    signIn: "/login",
  },
  callbacks: {
    // Callback when the JWT is created / updated, see https://next-auth.js.org/configuration/callbacks#jwt-callback
    jwt: async ({ token, user }) => {
      console.log('jwt...')
      const isSignIn = user ? true : false;
      if (isSignIn) {
        token.jwt = user ? (user as any).access_token || '' : '';
        token.id = user ? user.id || '' : '';
        token.role = user ? (user as any).role || '' : '';
        token.foo = 'foo'
      }
      return Promise.resolve(token);
    },
    // Callback whenever session is checked, see https://next-auth.js.org/configuration/callbacks#session-callback
    session: async (all) => {
      (all.session as any).role = all.token.role;
      (all.session as any).uid = all.token.id;
      (all.session as any).bar = 'bar';
      console.log('session...')
      console.log(all)
      return Promise.resolve(all.session);
    },
  },
  providers: [
    {
      id: "laravelpassport", //ID is only used for the callback URL
      name: "Passport", // name is used for the login button
      type: "oauth", // connexion type
      version: "2.0", // oauth version
      authorization: {
        url: `${passport.baseUrl}/oauth/authorize`, // this is the route created by passport by default to get the autorization code
        params: {
          scope: "*", // this is the wildcard for all scopes in laravel passport, you can specify scopes separated by a space
        },
      },
      token: {
        url: `${passport.baseUrl}/oauth/token`, // this is the default route created by passport to get and renew the tokens
      },
      clientId: passport.clientId, // the client Id
      clientSecret: passport.clientSecret, // the client secret
      userinfo: {
        url: `${passport.baseUrl}/backoffice/v2/user`, // this is a custom route that must return the current user that must be created in laravel
      },
      async authorize(credentials: any) {
        console.log('authorize passed')
        const data = {
          user: {
            name: 'John Doe',
            email: 'john@email.com',
            id: 1,
            role: 'admin'
          },
          token: '123456890',
        }
        if (data.user) {
          const u = {
            id: data.user.id,
            name: data.user.username,
            email: data.user.email,
            access_token: data.token, // additional field
            role: response.user.role // additional field
          };
          return u;
        } else {
          throw createError({
            statusCode: 403,
            statusMessage: "Credentials not working",
          });
        }
      },
      profile: (profile) => {
        // console.log(profile)

        // TEST COOKIE => useCookie is not defined
        // const foo = useCookie('foo')
        // foo.value = 'bar'

        // TEST PINIA => 500: Cannot access 'renderer$1' before initialization
        // const userStore = useUserStore();

        // access_token is filtered out
        return {
          id: profile.user.id,
          name: profile.user.first_name,
          email: profile.user.email,
          access_token: profile.authorizationBearer
        };
      },
      idToken: false,
    },
  ],
});

However, async authorize is never passed. Any idea to fix this issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A bug that needs to be resolved pending
Projects
None yet
Development

No branches or pull requests

2 participants