The AdonisJS Starter Kit includes pre-configured social authentication using AdonisJS Ally. Users can sign in with their Google or GitHub accounts, with automatic account creation for first-time users.
The SocialController handles the complete OAuth flow:
app/auth/controllers/social_controller.ts
import type { HttpContext } from '@adonisjs/core/http'import { afterAuthRedirectRoute } from '#config/auth'import User from '#users/models/user'export default class SocialController { // Step 1: Redirect to OAuth provider async redirect({ ally, params }: HttpContext) { const driverInstance = ally.use(params.provider) return driverInstance.redirect() } // Step 2: Handle OAuth callback async callback({ ally, auth, params, response, session }: HttpContext) { const social = ally.use(params.provider) // User denied access if (social.accessDenied()) { session.flash('errors', 'auth.social.error.access_denied') return response.redirect().toRoute('auth.sign_up.show') } // CSRF token mismatch if (social.stateMisMatch()) { session.flash('errors', 'auth.social.error.state_mismatch') return response.redirect().toRoute('auth.sign_up.show') } // Provider error if (social.hasError()) { session.flash('errors', 'auth.social.error.uknown') return response.redirect().toRoute('auth.sign_up.show') } // Get user info from provider const socialUser = await social.user() // Find or create user let user = await User.findBy('email', socialUser.email) if (!user) { user = await User.create({ fullName: socialUser.name, email: socialUser.email, password: null, avatarUrl: socialUser.avatarUrl, }) } // Log user in await auth.use('web').login(user) return response.redirect().toRoute(afterAuthRedirectRoute) }}
Automatic Account Creation: If a user signs in with a social provider for the first time, a new account is automatically created using their social profile information.
# Google OAuthGOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.comGOOGLE_CLIENT_SECRET=your-google-client-secret# GitHub OAuth (if using)GITHUB_CLIENT_ID=your-github-client-idGITHUB_CLIENT_SECRET=your-github-client-secret# Base URLVITE_API_URL=http://localhost:3000
export default class User extends compose(BaseModel, AuthFinder) { @column() declare fullName: string | null @column() declare email: string @column({ serializeAs: null }) declare password: string | null // Null for social auth users @column() declare avatarUrl: string | null // From social profile}
Password Field: Users who sign up via social authentication have a null password. They must use social login or go through password reset to set a password.
The current implementation automatically links social accounts to existing accounts with matching email addresses:
let user = await User.findBy('email', socialUser.email)if (!user) { // Create new account user = await User.create({ ... })}// Log in existing or new userawait auth.use('web').login(user)