eidellev / inertiajs-adonisjs

278 stars 17 forks source link

Authentication is not working properly. #116

Closed jeancastellotti closed 1 year ago

jeancastellotti commented 1 year ago

Hi,

I'm trying to set up an authentication with Adonis and Inertia. Although I followed the documentation, I have the same error that shows up in the browser when I click on a link that points to a page where the user should be logged in.

For example, the page where the posts are displayed.

Capture d'écran 2023-03-20 200702

Capture d'écran 2023-03-20 203124

But when I enter the url directly, it works. I am redirected to the login page.

resources/js/app.js

import '../css/app.css'
import { createApp, h } from 'vue'
import { createInertiaApp, Head } from '@inertiajs/vue3'
import Layout from './Shared/Layout'

createInertiaApp({
  progress: false,
  resolve: (name) => {
    const page = require(`./Pages/${name}`).default
    page.layout = name.startsWith('Auth/') ? undefined : Layout
    return page
  },
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .component('Head', Head)
      .mount(el)
  },
})

app/Exceptions/Handler.ts

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler'
import Logger from '@ioc:Adonis/Core/Logger'

export default class ExceptionHandler extends HttpExceptionHandler {
  protected statusPages = {
    '403': 'errors/unauthorized',
    '404': 'errors/not-found',
    '500..599': 'errors/server-error',
  }

  constructor() {
    super(Logger)
  }

  public async handle(error: any, ctx: HttpContextContract) {
    const { session, response } = ctx

    if (['E_INVALID_AUTH_PASSWORD', 'E_INVALID_AUTH_UID'].includes(error.code)) {
      session.flash('errors', { login: error.message })
      return response.redirect('/login')
    }

    return super.handle(error, ctx)
  }
}

start/kernel.ts

import Server from '@ioc:Adonis/Core/Server'

Server.middleware.register([
  () => import('@ioc:Adonis/Core/BodyParser'),
  () => import('@ioc:EidelLev/Inertia/Middleware'),
])

Server.middleware.registerNamed({
  auth: () => import('App/Middleware/Auth'),
})
jeancastellotti commented 1 year ago

Am I the only facing this issue ? :D

eidellev commented 1 year ago

Hey @JeanDevFR Sorry it took me some time to respond. I personally haven't run into any issues with this. It appears that your /login route is not responding with the page component name. can you please share your route config and controller method for this route?

jeancastellotti commented 1 year ago

start/routes.ts

import Route from '@ioc:Adonis/Core/Route'

Route.inertia('/', 'Home')

Route.group(() => {
  Route.get('/posts', 'PostsController.index')
  // ...
}).middleware('auth')

Route.get('/login', 'AuthController.showLogin')
Route.post('/login', 'AuthController.login')
Route.post('/logout', 'AuthController.logout').middleware('auth')

app/Controllers/Http/AuthController.ts

import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

export default class AuthController {
  public async showLogin({ inertia }: HttpContextContract) {
    return inertia.render('Auth/Login')
  }

  public async login({ request, response, auth }: HttpContextContract) {
    // ...
    await auth.attempt(email, password)
    return response.redirect('/')
  }

  public async logout({ response, auth }: HttpContextContract) {
    await auth.logout()
    return response.redirect('/login')
  }
}

Do you think this has something to do with the auth middleware from Adonis throwing an AuthenticationException?

app/Middleware/Auth.ts

import { AuthenticationException } from '@adonisjs/auth/build/standalone'
import type { GuardsList } from '@ioc:Adonis/Addons/Auth'
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

/**
 * Auth middleware is meant to restrict un-authenticated access to a given route
 * or a group of routes.
 *
 * You must register this middleware inside `start/kernel.ts` file under the list
 * of named middleware.
 */
export default class AuthMiddleware {
  /**
   * The URL to redirect to when request is Unauthorized
   */
  protected redirectTo = '/login'

  /**
   * Authenticates the current HTTP request against a custom set of defined
   * guards.
   *
   * The authentication loop stops as soon as the user is authenticated using any
   * of the mentioned guards and that guard will be used by the rest of the code
   * during the current request.
   */
  protected async authenticate(auth: HttpContextContract['auth'], guards: (keyof GuardsList)[]) {
    /**
     * Hold reference to the guard last attempted within the for loop. We pass
     * the reference of the guard to the "AuthenticationException", so that
     * it can decide the correct response behavior based upon the guard
     * driver
     */
    let guardLastAttempted: string | undefined

    for (let guard of guards) {
      guardLastAttempted = guard

      if (await auth.use(guard).check()) {
        /**
         * Instruct auth to use the given guard as the default guard for
         * the rest of the request, since the user authenticated
         * succeeded here
         */
        auth.defaultGuard = guard
        return true
      }
    }

    /**
     * Unable to authenticate using any guard
     */
    throw new AuthenticationException(
      'Unauthorized access',
      'E_UNAUTHORIZED_ACCESS',
      guardLastAttempted,
      this.redirectTo
    )
  }

  /**
   * Handle request
   */
  public async handle(
    { auth }: HttpContextContract,
    next: () => Promise<void>,
    customGuards: (keyof GuardsList)[]
  ) {
    /**
     * Uses the user defined guards or the default guard mentioned in
     * the config file
     */
    const guards = customGuards.length ? customGuards : [auth.name]
    await this.authenticate(auth, guards)
    await next()
  }
}

Should we modify the app/Exceptions/Handler.ts file to handle this?

eidellev commented 1 year ago

not sure. nothing stands out as a possible reason for this issue. can you create an example repo where this can be reproduced?

jeancastellotti commented 1 year ago

Yep.

Quick setup using sqlite3.

https://github.com/JeanDevFR/adonis-inertia-auth

eidellev commented 1 year ago

Thanks! I've managed to figure out what went wrong. This is how you need to set up your Auth middleware:

 /**
   * Handle request
   */
  public async handle(
    { auth, response }: HttpContextContract,
    next: () => Promise<void>,
    customGuards: (keyof GuardsList)[],
  ) {
    /**
     * Uses the user defined guards or the default guard mentioned in
     * the config file
     */
    const guards = customGuards.length ? customGuards : [auth.name];
    try {
      await this.authenticate(auth, guards);
      await next();
    } catch (error) {
      if (error.redirectTo) {
        return response.redirect(this.redirectTo);
      }

      throw error;
    }
  }

We have to overwrite Adonis's default 401 response with a valid inertia 200 response. I will add this to the readme. Feel free to reopen if you run into more issues.