Nuda Kit uses AdonisJS Ally to handle social authentication. It comes pre-configured with support for Google, GitHub, and Facebook, but you can easily add Twitter, Discord, LinkedIn, and many others.
The OAuth flow in a decoupled (Nuxt + AdonisJS) environment works like this:
The user clicks a social login button (e.g., "Login with Google") on the frontend.
The browser navigates to your API endpoint (e.g., /users/login/google), which redirects to the provider's OAuth page.
The user accepts permissions on the provider's page (Google, GitHub, or Facebook).
The provider redirects back to your API callback endpoint (e.g., /users/login/google/callback). AdonisJS verifies the token and retrieves the user's profile.
The system either finds an existing user by email or creates a new account with the profile data from the provider.
A JWT access token is generated, and the user is redirected back to your Nuxt frontend with the token as a query parameter (e.g., /auth/login?token={JWT_TOKEN}).
The following routes are registered for social authentication:
| Provider | Redirect URL | Callback URL |
|---|---|---|
GET /users/login/google | GET /users/login/google/callback | |
| GitHub | GET /users/login/github | GET /users/login/github/callback |
GET /users/login/facebook | GET /users/login/facebook/callback |
To enable a social provider, you need to obtain the Client ID and Client Secret from the provider's developer console.
https://api.yourdomain.com/users/login/google/callbackAdd the credentials to your backend/.env file:
# Google
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
GOOGLE_CALLBACK_URL=http://localhost:3333/users/login/google/callback
# GitHub
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
GITHUB_CALLBACK_URL=http://localhost:3333/users/login/github/callback
# Facebook
FACEBOOK_CLIENT_ID=your_facebook_client_id
FACEBOOK_CLIENT_SECRET=your_facebook_client_secret
FACEBOOK_CALLBACK_URL=http://localhost:3333/users/login/facebook/callback
The social providers are configured in backend/config/ally.ts:
import env from '#start/env'
import { defineConfig, services } from '@adonisjs/ally'
const allyConfig = defineConfig({
facebook: services.facebook({
clientId: env.get('FACEBOOK_CLIENT_ID') || '',
clientSecret: env.get('FACEBOOK_CLIENT_SECRET') || '',
callbackUrl: env.get('FACEBOOK_CALLBACK_URL') || '',
}),
github: services.github({
clientId: env.get('GITHUB_CLIENT_ID') || '',
clientSecret: env.get('GITHUB_CLIENT_SECRET') || '',
callbackUrl: env.get('GITHUB_CALLBACK_URL') || '',
}),
google: services.google({
clientId: env.get('GOOGLE_CLIENT_ID') || '',
clientSecret: env.get('GOOGLE_CLIENT_SECRET') || '',
callbackUrl: env.get('GOOGLE_CALLBACK_URL') || '',
}),
})
export default allyConfig
On the frontend, social login is triggered by navigating to the API endpoint. Here's how it's implemented in LoginWithEmailPasswordForm.vue:
<script setup lang="ts">
const config = useRuntimeConfig()
const onLoginWithGoogle = () => {
navigateTo(`${config.public.apiBaseUrl}/users/login/google`, { external: true })
}
const onLoginWithGithub = () => {
navigateTo(`${config.public.apiBaseUrl}/users/login/github`, { external: true })
}
const onLoginWithFacebook = () => {
navigateTo(`${config.public.apiBaseUrl}/users/login/facebook`, { external: true })
}
</script>
The login page (frontend/app/pages/app/auth/login.vue) automatically handles the token returned from the OAuth flow:
<script setup lang="ts">
const { setToken } = useAuthCookie()
const queryClient = useQueryClient()
onMounted(() => {
const token = useRoute().query.token
if (token) {
setToken(token as string)
queryClient.invalidateQueries({ queryKey: ['teams'] })
toast.success('Logged in successfully')
navigateTo('/app')
}
})
</script>
The OAuth callback handles several error scenarios:
| Error | Description |
|---|---|
access_denied | User denied the permission request |
state_mismatch | OAuth state mismatch (possible CSRF attack) |
error | Generic error from the provider |
user_marked_for_deletion | User account is scheduled for deletion |
Errors are passed back to the frontend as query parameters: /auth/login?error=true&error_message=access_denied
AdonisJS Ally supports many OAuth providers out of the box. To add a new provider:
backend/config/ally.ts:const allyConfig = defineConfig({
// ... existing providers
twitter: services.twitter({
clientId: env.get('TWITTER_CLIENT_ID') || '',
clientSecret: env.get('TWITTER_CLIENT_SECRET') || '',
callbackUrl: env.get('TWITTER_CALLBACK_URL') || '',
}),
})
backend/start/env.ts:TWITTER_CLIENT_ID: Env.schema.string.optional(),
TWITTER_CLIENT_SECRET: Env.schema.string.optional(),
TWITTER_CALLBACK_URL: Env.schema.string.optional(),
backend/app/controllers/users/login_user_google_controller.ts.backend/start/routes/users.ts:router.get('/login/twitter', [LoginUserTwitterController, 'redirect'])
router.get('/login/twitter/callback', [LoginUserTwitterController, 'callback'])