Adonis.js: Returning the user to the previously requested URL after authentication

Emelia Smith
3 min readMay 19, 2024
Photo by Onur Binay on Unsplash

I’m currently working on an Adonis.js codebase, and one of the things that had been mildly annoying me was the fact that after login, I was not taken back to the page I’d originally requested. Instead I was always taken to the same page, in this example /admin .

Asking in the Adonis.js project discord, I received a hint that I could modify my AuthMiddleware to add extra logic for handling this. Initially I’d totally missed that there even was an AuthMiddleware in my project, it’s located in the app/middleware/auth_middleware.ts file.

Once I had that small tip, it was quite easy to change to the AuthMiddleware to store the requested URL, including the query string, in the session:

--- a/webapp/app/middleware/auth_middleware.ts
+++ b/webapp/app/middleware/auth_middleware.ts
@@ -1,6 +1,9 @@
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
import type { Authenticators } from '@adonisjs/auth/types'
+import { errors } from '@adonisjs/auth'
+
+export const returnToKey = 'return_to'

/**
* Auth middleware is used authenticate HTTP requests and deny
@@ -19,7 +22,15 @@ export default class AuthMiddleware {
guards?: (keyof Authenticators)[]
} = {}
) {
- await ctx.auth.authenticateUsing(options.guards, {
- loginRoute: this.redirectTo
- })
+ try {
+ await ctx.auth.authenticateUsing(options.guards, {
+ loginRoute: this.redirectTo
+ })
+ } catch (err) {
+ if (err instanceof errors.E_UNAUTHORIZED_ACCESS) {
+ // Place the current URL including the query string in the Session:
+ ctx.session.put(returnToKey, ctx.request.url(true))
+ }
+
+ throw err
+ }
return next()
}
}

The export of returnToKey is so that we can later use the same session key with ease, because if you accidentally get the session key to be different between the two files, you’ll be left scratching your head (I accidentally did this whilst implementing).

You can also catch other errors here, such as errors.E_INVALID_CREDENTIALS which is potentially thrown when you’re using the withAuthFinder mixin on your User model and performing authentication in the way that’s described in documentation.

In the application I’m working on, we’re not using the withAuthFinder method for authentication, and are instead using an a third-party provider via an OAuth/OIDC redirect based flow.

In our SessionController, I just needed to change our fixed URL redirect once authentication had completed successfully from the following:

// In our login handler:
await auth.use('web').login(user)
response.redirect().toPath("/admin")

(where user is an instance of the model we configured our auth guard with)

To the following:

// Add the import:
import { returnToKey } from '#middleware/auth_middleware'

// In our login handler:
const returnTo = session.pull(returnToKey, '/admin')

// Wipe the session to prevent session fixation (this
// is more necessary for OAuth based flows):
session.regenerate()

// find the user
const user = // ...

await auth.use('web').login(user)
response.redirect().toPath(returnTo)

This then gives us an authentication flow that takes us back to the previously requested URL after login.

Security Note #1: If you have open redirects in your application, you should absolutely filter the returnTo URL to exclude those endpoints, otherwise you can be susceptible to phishing and other attacks.

Security Note #2: If you’re doing a custom login flow with additional session parameters, you should always call session.regenerate to prevent session fixation attacks

--

--

Emelia Smith

Founder of Unobvious Technology UG, survivor of startups, tech princess. You probably use or benefit from my code.