Authentication with Supabase Auth + Vue + NestJS [Implicit flow]

Step 1: Google Cloud console settings
Link: https://console.cloud.google.com/apis/credentials?
You need to have google cloud project. Then go to Clients → OAuth 2.0 Client IDs
- Add the supabase project url to the origins and redirect urls
<PROJECT_ID>.
supabase.co
- Now you have to take note these 2 keys: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
These keys gonna be used in the next step.
Step 2: Supabase Auth settings
- Go to Authentication → Sign in / Provider. In the Auth Providers list, enable Google
- Then click to the Google to setup. Paste above two key: GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET into below. Also save the redirect url to SUPABASE_CALLBACK_URL
- After authenticated, you have to redirect the user to an url, the url need to be listed in the below settings:
Step 3: Frontend setup
Here I’m using Vue (quasar for the components), but it basically the same for all frontend frameworks.
Install: npm install @supabase/supabase-js
- Create a supabase client:
supabase.ts
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.SUPABASE_URL as string;
const supabaseKey = process.env.SUPABASE_KEY as string;
export const supabase = createClient(supabaseUrl, supabaseKey);
- just need to call
signInWithOAuth
from above instance.
After authentication it gonna be navigated to SUPABASE_CALLBACK_URL
that we declare above
const signIn = async () => {
await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `SUPABASE_CALLBACK_URL`,
},
});
};
And this is data it send back to the callback:
access_token=eyJhbGciOiJIUzI1NiIsImtpZCI6IlA5MHlVN3NwL2RzUFhzeUEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2ZweWh6dHlneGtoaHVibW5vYXV4LnN1cGFiYXNlLmNvL2F1dGgvdjEiLCJzdWIiOiJmOGFjOWEyOC05MGVhLTQ0M2UtYTM0Yi00YThhMmYyYjcwZjEiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzUzMDE4MjQwLCJpYXQiOjE3NTMwMTQ2NDAsImVtYWlsIjoiY29uZy5ob2FuZ0BzbGVlay5jb20iLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6Imdvb2dsZSIsInByb3ZpZGVycyI6WyJnb29nbGUiXX0sInVzZXJfbWV0YWRhdGEiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hL0FDZzhvY0lMRk0zZFRjdW40ZTQ2djE4aG4xZFFNV1V1UHFHQWVXNktSWExLYXhEUFFKZ21Zdz1zOTYtYyIsImN1c3RvbV9jbGFpbXMiOnsiaGQiOiJzbGVlay5jb20ifSwiZW1haWwiOiJjb25nLmhvYW5nQHNsZWVrLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmdWxsX25hbWUiOiJDb25nIEhvYW5nIiwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwibmFtZSI6IkNvbmcgSG9hbmciLCJwaG9uZV92ZXJpZmllZCI6ZmFsc2UsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NJTEZNM2RUY3VuNGU0NnYxOGhuMWRRTVdVdVBxR0FlVzZLUlhMS2F4RFBRSmdtWXc9czk2LWMiLCJwcm92aWRlcl9pZCI6IjExMDY0NjM0MjgyMzY2NDMzMTg0MSIsInN1YiI6IjExMDY0NjM0MjgyMzY2NDMzMTg0MSJ9LCJyb2xlIjoiYXV0aGVudGljYXRlZCIsImFhbCI6ImFhbDEiLCJhbXIiOlt7Im1ldGhvZCI6Im9hdXRoIiwidGltZXN0YW1wIjoxNzUzMDE0NjQwfV0sInNlc3Npb25faWQiOiIyMWM3ZTg3Yy03MTUxLTRlZmMtOWM4Yi0yNzVkYzU3MWNiZjUiLCJpc19hbm9ueW1vdXMiOmZhbHNlfQ.rZWA7NJKytBsqK27W3Vv9uuDaeDnzv6_RdHNf0nYW5M&expires_at=1753018240&expires_in=3600&provider_token=ya29.a0AS3H6NwoQaW7XYww3TUbZgAdcTD7W6_86hnnpvKVwPQhTMPFdlEIHTfy09ZCm8GAaq5o_U8kk7Cf3aG3GqkySypzPchRcB8J1Vvj65nC5WKE9FcB8-FalwvC2DgWW4CUyP6uwhq09WwjRXdU_atWJ424M76FzyrwiOkD0vB7FdoaCgYKAR8SARcSFQHGX2MiYJlTLLqt8Etq-xARlCbS1g0178&refresh_token=w4jw5h76h3c3&token_type=bearer
So for the simplicity now we can save the accessToken above to localStorage and use it as authentication for the request to the backend.
- Update user in the store whenever page is loaded
Handle this event onAuthStateChange
to match with your need on handle auth in the app.
Below is my current implementation.
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { supabase } from "@/supabase";
import { useUserStore } from "@/stores/userStore";
export const AuthHandler = () => {
const navigate = useNavigate();
const { setUser, clearUser } = useUserStore();
useEffect(() => {
console.log("Setting up Supabase auth listener...");
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
console.log("🔐 Supabase Auth Event:", event);
console.log("👤 Session:", session);
switch (event) {
case 'SIGNED_IN':
console.log("✅ User signed in successfully");
console.log("User ID:", session?.user?.id);
console.log("User Email:", session?.user?.email);
// Store user data in Zustand store
if (session?.user) {
setUser(session.user);
}
break;
case 'SIGNED_OUT':
console.log("❌ User signed out");
// Clear user data from Zustand store
clearUser();
// Navigate to home page
console.log("🏠 Navigating to home page after logout");
navigate("/");
break;
default:
console.log("❓ Unknown auth event:", event);
}
}
);
// Cleanup subscription on unmount
return () => {
console.log("Cleaning up Supabase auth listener...");
subscription.unsubscribe();
};
}, [setUser, clearUser, navigate]);
return null; // This component doesn't render anything
};
Step 4: Validate token in backend
We already have the token from above step, so whenever user call API let add it to the header→authentication, now we create a guard to validate it.
auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { createClient, SupabaseClient } from '@supabase/supabase-js';
@Injectable()
export class AuthGuard implements CanActivate {
private supabase: SupabaseClient;
constructor(private readonly configService: ConfigService) {
this.supabase = createClient(
this.configService.get('SUPABASE_URL'),
this.configService.get('SUPABASE_KEY'),
);
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const accessToken = request.headers['authorization'];
if (!accessToken) {
return false;
}
const user = await this.supabase.auth.getUser(accessToken);
if (!user.data.user) {
return false;
}
request.user = user.data.user;
return true;
}
}
I just create simple authentication here.
Now we add it to our endpoint/controller:
@Controller()
export class AppController {
@Get('user')
@UseGuards(AuthGuard)
getUser(@Req() req: Request) {
return req['user'];
}
}
—
So now whenever we got 403, we should navigate user to login page.
Refs:
https://supabase.com/docs/guides/auth/social-login/auth-google
Subscribe to my newsletter
Read articles from The Brown Box directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
