See the best posts from any Bluesky account
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Set up AdonisJS session auth guard, middleware, and test infrastructure

Configure @adonisjs/auth with session guard for the Account model,
register the auth provider and initialize_auth_middleware, add auth/guest
named middleware, and wire up the Japa session+auth API client plugins
so loginAs() works in functional tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+177 -1
+1
adonisrc.ts
··· 63 63 () => import('@adonisjs/static/static_provider'), 64 64 () => import('@adonisjs/lucid/database_provider'), 65 65 () => import('@adonisjs/queue/queue_provider'), 66 + () => import('@adonisjs/auth/auth_provider'), 66 67 () => import('#providers/atproto_provider'), 67 68 () => import('#providers/clickhouse_provider'), 68 69 ],
+37
app/middleware/auth_middleware.ts
··· 1 + import type { Authenticators } from '@adonisjs/auth/types' 2 + import type { HttpContext } from '@adonisjs/core/http' 3 + import type { NextFn } from '@adonisjs/core/types/http' 4 + 5 + /** 6 + * Auth middleware protects routes from unauthenticated access. 7 + * 8 + * For browser routes it redirects to the home page. For API routes 9 + * (paths starting with /api/) it returns a 401 JSON response. 10 + */ 11 + export default class AuthMiddleware { 12 + redirectTo = '/' 13 + 14 + async handle( 15 + ctx: HttpContext, 16 + next: NextFn, 17 + options: { guards?: (keyof Authenticators)[] } = {} 18 + ) { 19 + const guards = options.guards ?? (['web'] as (keyof Authenticators)[]) 20 + 21 + try { 22 + await ctx.auth.authenticateUsing(guards) 23 + return next() 24 + } catch { 25 + // Not authenticated 26 + } 27 + 28 + /** 29 + * For API routes, return a 401 JSON response instead of redirecting. 30 + */ 31 + if (ctx.request.url().startsWith('/api/')) { 32 + return ctx.response.unauthorized({ message: 'Unauthorized' }) 33 + } 34 + 35 + return ctx.response.redirect().toPath(this.redirectTo) 36 + } 37 + }
+27
app/middleware/guest_middleware.ts
··· 1 + import type { Authenticators } from '@adonisjs/auth/types' 2 + import type { HttpContext } from '@adonisjs/core/http' 3 + import type { NextFn } from '@adonisjs/core/types/http' 4 + 5 + /** 6 + * Guest middleware redirects authenticated users away from 7 + * guest-only pages (e.g. login). 8 + */ 9 + export default class GuestMiddleware { 10 + redirectTo = '/' 11 + 12 + async handle( 13 + ctx: HttpContext, 14 + next: NextFn, 15 + options: { guards?: (keyof Authenticators)[] } = {} 16 + ) { 17 + const guards = options.guards ?? (['web'] as (keyof Authenticators)[]) 18 + 19 + for (const guard of guards) { 20 + if (await ctx.auth.use(guard).check()) { 21 + return ctx.response.redirect().toPath(this.redirectTo) 22 + } 23 + } 24 + 25 + return next() 26 + } 27 + }
+21
config/auth.ts
··· 1 + import { defineConfig } from '@adonisjs/auth' 2 + import { sessionGuard, sessionUserProvider } from '@adonisjs/auth/session' 3 + import type { InferAuthenticators } from '@adonisjs/auth/types' 4 + 5 + const authConfig = defineConfig({ 6 + default: 'web', 7 + guards: { 8 + web: sessionGuard({ 9 + useRememberMeTokens: false, 10 + provider: sessionUserProvider({ 11 + model: () => import('#models/account'), 12 + }), 13 + }), 14 + }, 15 + }) 16 + 17 + export default authConfig 18 + 19 + declare module '@adonisjs/auth/types' { 20 + export interface Authenticators extends InferAuthenticators<typeof authConfig> {} 21 + }
+5 -1
start/kernel.ts
··· 37 37 () => import('@adonisjs/core/bodyparser_middleware'), 38 38 () => import('@adonisjs/session/session_middleware'), 39 39 () => import('@adonisjs/shield/shield_middleware'), 40 + () => import('@adonisjs/auth/initialize_auth_middleware'), 40 41 ]) 41 42 42 43 /** 43 44 * Named middleware collection must be explicitly assigned to 44 45 * the routes or the routes group. 45 46 */ 46 - export const middleware = router.named({}) 47 + export const middleware = router.named({ 48 + auth: () => import('#middleware/auth_middleware'), 49 + guest: () => import('#middleware/guest_middleware'), 50 + })
+26
start/routes.ts
··· 8 8 */ 9 9 10 10 import router from '@adonisjs/core/services/router' 11 + import { middleware } from '#start/kernel' 11 12 12 13 const LandingController = () => import('#controllers/landing_controller') 13 14 const SearchController = () => import('#controllers/search_controller') ··· 82 83 return response.unauthorized({ message: 'Unauthorized' }) 83 84 }) 84 85 .as('health.ready') 86 + 87 + // --------------------------------------------------------------------------- 88 + // Auth test routes (only used in tests — protected/guest route stubs) 89 + // --------------------------------------------------------------------------- 90 + 91 + router 92 + .get('/auth/me', async ({ auth, response }) => { 93 + return response.ok({ did: auth.user!.did, handle: auth.user!.handle }) 94 + }) 95 + .use(middleware.auth()) 96 + .as('auth.me') 97 + 98 + router 99 + .get('/api/auth/me', async ({ auth, response }) => { 100 + return response.ok({ did: auth.user!.did, handle: auth.user!.handle }) 101 + }) 102 + .use(middleware.auth()) 103 + .as('api.auth.me') 104 + 105 + router 106 + .get('/auth/guest-only', async ({ response }) => { 107 + return response.ok({ message: 'guest page' }) 108 + }) 109 + .use(middleware.guest()) 110 + .as('auth.guest')
+4
tests/bootstrap.ts
··· 4 4 import app from '@adonisjs/core/services/app' 5 5 import type { Config } from '@japa/runner/types' 6 6 import { pluginAdonisJS } from '@japa/plugin-adonisjs' 7 + import { sessionApiClient } from '@adonisjs/session/plugins/api_client' 8 + import { authApiClient } from '@adonisjs/auth/plugins/api_client' 7 9 import { dbAssertions } from '@adonisjs/lucid/plugins/db' 8 10 import testUtils from '@adonisjs/core/services/test_utils' 9 11 ··· 18 20 export const plugins: Config['plugins'] = [ 19 21 assert(), 20 22 apiClient(), 23 + sessionApiClient(app), 24 + authApiClient(app), 21 25 pluginAdonisJS(app), 22 26 dbAssertions(app), 23 27 browserClient({
+56
tests/functional/auth.spec.ts
··· 1 + /** 2 + * Functional tests for AdonisJS auth setup. 3 + * 4 + * Verifies: 5 + * - Unauthenticated requests to protected routes get redirected (or 401 for API) 6 + * - After logging in an Account, auth state persists across requests 7 + */ 8 + import { test } from '@japa/runner' 9 + import testUtils from '@adonisjs/core/services/test_utils' 10 + import Account from '#models/account' 11 + 12 + test.group('Auth middleware', (group) => { 13 + group.each.setup(() => testUtils.db().withGlobalTransaction()) 14 + 15 + test('unauthenticated request to a protected route returns 302 redirect', async ({ client }) => { 16 + const response = await client.get('/auth/me').redirects(0) 17 + response.assertStatus(302) 18 + }) 19 + 20 + test('authenticated request to a protected route returns 200', async ({ client }) => { 21 + const account = await Account.create({ 22 + did: 'did:plc:testuser123', 23 + handle: 'test.bsky.social', 24 + sessionData: '{}', 25 + createdAt: Date.now(), 26 + updatedAt: Date.now(), 27 + }) 28 + 29 + const response = await client.get('/auth/me').loginAs(account) 30 + response.assertStatus(200) 31 + response.assertBodyContains({ did: 'did:plc:testuser123', handle: 'test.bsky.social' }) 32 + }) 33 + 34 + test('unauthenticated API request returns 401 JSON', async ({ client }) => { 35 + const response = await client.get('/api/auth/me') 36 + response.assertStatus(401) 37 + }) 38 + 39 + test('guest middleware redirects authenticated users', async ({ client }) => { 40 + const account = await Account.create({ 41 + did: 'did:plc:guestuser456', 42 + handle: 'guest.bsky.social', 43 + sessionData: '{}', 44 + createdAt: Date.now(), 45 + updatedAt: Date.now(), 46 + }) 47 + 48 + const response = await client.get('/auth/guest-only').loginAs(account).redirects(0) 49 + response.assertStatus(302) 50 + }) 51 + 52 + test('guest middleware allows unauthenticated users', async ({ client }) => { 53 + const response = await client.get('/auth/guest-only') 54 + response.assertStatus(200) 55 + }) 56 + })