Skip to content

Add Supabase authentication and protect routes #320

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Supabase credentials
VITE_SUPABASE_URL=your-supabase-url
VITE_SUPABASE_ANON_KEY=your-supabase-anon-key

# Other environment variables can be added here
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ node_modules/
tests/e2e/reports/

# local env files
.env
.env.local
.env.*.local

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@coreui/vue": "^5.4.1",
"@coreui/vue-chartjs": "^3.0.0",
"@popperjs/core": "^2.11.8",
"@supabase/supabase-js": "^2.49.4",
"chart.js": "^4.4.7",
"pinia": "^3.0.1",
"simplebar-vue": "^2.4.0",
Expand Down
23 changes: 21 additions & 2 deletions src/components/AppHeaderDropdownAccnt.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
<script setup>
import avatar from '@/assets/images/avatars/8.jpg'
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import supabase from '@/lib/supabase'

const itemsCount = 42
const router = useRouter()
const userName = ref('Account')

onMounted(async () => {
const { data } = await supabase.auth.getUser()
if (data.user) {
userName.value = data.user.user_metadata?.displayName || data.user.email || 'Account'
}
})

const handleLogout = async () => {
await supabase.auth.signOut()
router.push('/pages/login')
}
</script>

<template>
Expand All @@ -14,7 +31,7 @@ const itemsCount = 42
component="h6"
class="bg-body-secondary text-body-secondary fw-semibold mb-2 rounded-top"
>
Account
{{ userName }}
</CDropdownHeader>
<CDropdownItem>
<CIcon icon="cil-bell" /> Updates
Expand Down Expand Up @@ -50,7 +67,9 @@ const itemsCount = 42
</CDropdownItem>
<CDropdownDivider />
<CDropdownItem> <CIcon icon="cil-shield-alt" /> Lock Account </CDropdownItem>
<CDropdownItem> <CIcon icon="cil-lock-locked" /> Logout </CDropdownItem>
<CDropdownItem @click="handleLogout">
<CIcon icon="cil-lock-locked" /> Logout
</CDropdownItem>
</CDropdownMenu>
</CDropdown>
</template>
8 changes: 8 additions & 0 deletions src/lib/supabase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY
);

export default supabase;
25 changes: 25 additions & 0 deletions src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,4 +316,29 @@ const router = createRouter({
},
})

// Navigation guard to protect routes
router.beforeEach(async (to, from, next) => {
// Public pages that do not require authentication
const publicPages = [
'/pages/login',
'/pages/register',
'/pages/404',
'/pages/500',
'/pages/auth-callback',
];
const isPublic = publicPages.includes(to.path);

// Check Supabase session
const { data } = await import('@/lib/supabase').then(m => m.default.auth.getSession());
const isAuthenticated = !!data.session;

if (!isAuthenticated && !isPublic) {
next('/pages/login');
} else if (isAuthenticated && to.path === '/pages/login') {
next('/dashboard');
} else {
next();
}
});

export default router
76 changes: 54 additions & 22 deletions src/views/pages/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,47 @@
<CCardGroup>
<CCard class="p-4">
<CCardBody>
<CForm>
<form @submit.prevent="handleLogin">
<h1>Login</h1>
<p class="text-body-secondary">Sign In to your account</p>
<CInputGroup class="mb-3">
<CInputGroupText>
<CIcon icon="cil-user" />
</CInputGroupText>
<CFormInput
placeholder="Username"
autocomplete="username"
/>
</CInputGroup>
<CInputGroup class="mb-4">
<CInputGroupText>
<CIcon icon="cil-lock-locked" />
</CInputGroupText>
<CFormInput
type="password"
placeholder="Password"
autocomplete="current-password"
/>
</CInputGroup>
<div class="mb-3">
<CInputGroup class="mb-3">
<CInputGroupText>
<CIcon icon="cil-envelope-closed" />
</CInputGroupText>
<CFormInput
v-model="email"
type="email"
placeholder="Email"
autocomplete="email"
/>
</CInputGroup>
</div>
<div class="mb-3">
<CInputGroup class="mb-4">
<CInputGroupText>
<CIcon icon="cil-lock-locked" />
</CInputGroupText>
<CFormInput
v-model="password"
type="password"
placeholder="Password"
autocomplete="current-password"
/>
</CInputGroup>
</div>
<CRow>
<CCol :xs="6">
<CButton color="primary" class="px-4"> Login </CButton>
<CButton color="primary" class="px-4" type="submit"> Login </CButton>
</CCol>
<CCol :xs="6" class="text-right">
<CButton color="link" class="px-0">
Forgot password?
</CButton>
</CCol>
</CRow>
</CForm>
<div v-if="errorMessage" class="alert alert-danger mt-2">{{ errorMessage }}</div>
</form>
</CCardBody>
</CCard>
<CCard class="text-white bg-primary py-5" style="width: 44%">
Expand All @@ -62,3 +70,27 @@
</CContainer>
</div>
</template>

<script setup>
import { ref } from "vue";
import { useRouter } from "vue-router";
import supabase from "@/lib/supabase";

const email = ref("");
const password = ref("");
const errorMessage = ref("");
const router = useRouter();

const handleLogin = async () => {
errorMessage.value = "";
const { data, error } = await supabase.auth.signInWithPassword({
email: email.value,
password: password.value,
});
if (error) {
errorMessage.value = error.message;
} else {
router.push("/dashboard");
}
};
</script>
41 changes: 35 additions & 6 deletions src/views/pages/Register.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@
<CInputGroupText>
<CIcon icon="cil-user" />
</CInputGroupText>
<CFormInput placeholder="Username" autocomplete="username" />
</CInputGroup>
<CInputGroup class="mb-3">
<CInputGroupText>@</CInputGroupText>
<CFormInput placeholder="Email" autocomplete="email" />
<CFormInput v-model="email" placeholder="Email" autocomplete="email" />
</CInputGroup>
<CInputGroup class="mb-3">
<CInputGroupText>
<CIcon icon="cil-lock-locked" />
</CInputGroupText>
<CFormInput
v-model="password"
type="password"
placeholder="Password"
autocomplete="new-password"
Expand All @@ -33,19 +30,51 @@
<CIcon icon="cil-lock-locked" />
</CInputGroupText>
<CFormInput
v-model="repeatPassword"
type="password"
placeholder="Repeat password"
autocomplete="new-password"
/>
</CInputGroup>
<div class="d-grid">
<CButton color="success">Create Account</CButton>
<CButton color="success" @click="handleRegister">Create Account</CButton>
</div>
</CForm>
<div v-if="errorMessage" class="alert alert-danger mt-2">{{ errorMessage }}</div>
<div v-if="successMessage" class="alert alert-success mt-2">{{ successMessage }}</div>
</CCardBody>
</CCard>
</CCol>
</CRow>
</CContainer>
</div>
</template>

<script setup>
import { ref } from "vue";
import supabase from "@/lib/supabase";

const email = ref("");
const password = ref("");
const repeatPassword = ref("");
const errorMessage = ref("");
const successMessage = ref("");

const handleRegister = async () => {
errorMessage.value = "";
successMessage.value = "";
if (password.value !== repeatPassword.value) {
errorMessage.value = "Passwords do not match.";
return;
}
const { data, error } = await supabase.auth.signUp({
email: email.value,
password: password.value,
});
if (error) {
errorMessage.value = error.message;
} else {
successMessage.value = "Registration successful. Please check your email to confirm your account.";
}
};
</script>
4 changes: 4 additions & 0 deletions supabase/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Supabase
.branches
.temp
.env
Loading