diff --git a/main.go b/main.go
index fe42959..4c33ad4 100644
--- a/main.go
+++ b/main.go
@@ -89,7 +89,7 @@ func main() {
 			AuthURL:  cfg.OAuth.AuthURL,
 			TokenURL: cfg.OAuth.TokenURL,
 		},
-		RedirectURL: cfg.OAuth.AuthURL,
+		RedirectURL: fmt.Sprintf("http://localhost:%d/oauth/callback", cfg.Server.Port),
 		Scopes:      []string{"profile", "email"},
 	}
 
@@ -127,9 +127,7 @@ func main() {
 	e := echo.New()
 	e.Use(middleware.Logger())
 	e.Use(middleware.Recover())
-
-	// Serve static files
-	e.GET("/*", echo.WrapHandler(http.FileServer(http.FS(staticFiles))))
+	e.Use(middleware.CORS())
 
 	// OAuth2 routes
 	e.GET("/oauth/login", handleOAuthLogin)
@@ -146,6 +144,9 @@ func main() {
 		})
 	})
 
+	// Serve static files - must be after API routes
+	e.GET("/*", echo.WrapHandler(http.FileServer(http.FS(staticFiles))))
+
 	// Start server
 	addr := fmt.Sprintf(":%d", cfg.Server.Port)
 	logger.Info("Starting server", zap.Int("port", cfg.Server.Port))
@@ -190,10 +191,25 @@ func handleOAuthCallback(c echo.Context) error {
 	}
 
 	logger.Info("OAuth token exchanged successfully", zap.String("access_token", token.AccessToken))
-	return c.JSON(http.StatusOK, map[string]interface{}{
-		"access_token": token.AccessToken,
-		"expires_in":   token.Expiry,
-	})
+	
+	// Return HTML that stores the token and redirects to the main app
+	html := fmt.Sprintf(`
+	<!DOCTYPE html>
+	<html>
+	<head>
+		<title>Authentication Successful</title>
+		<script>
+			localStorage.setItem('auth_token', '%s');
+			window.location.href = '/';
+		</script>
+	</head>
+	<body>
+		<p>Authentication successful. Redirecting...</p>
+	</body>
+	</html>
+	`, token.AccessToken)
+	
+	return c.HTML(http.StatusOK, html)
 }
 
 // Middleware to enforce OAuth authentication
diff --git a/web/src/App.vue b/web/src/App.vue
index 9497da7..248a2cc 100644
--- a/web/src/App.vue
+++ b/web/src/App.vue
@@ -1,9 +1,21 @@
 <template>
   <v-app>
-    <router-view />
+    <v-main>
+      <router-view />
+    </v-main>
   </v-app>
 </template>
 
 <script lang="ts" setup>
-  //
+import { useAuthStore } from '@/stores/auth';
+import { onMounted } from 'vue';
+
+const authStore = useAuthStore();
+
+onMounted(async () => {
+  // If user has a token, fetch user info
+  if (authStore.isAuthenticated) {
+    await authStore.fetchUserInfo();
+  }
+});
 </script>
diff --git a/web/src/pages/auth.vue b/web/src/pages/auth.vue
new file mode 100644
index 0000000..e671e50
--- /dev/null
+++ b/web/src/pages/auth.vue
@@ -0,0 +1,39 @@
+<template>
+  <v-container class="fill-height">
+    <v-responsive class="align-center fill-height mx-auto" max-width="500">
+      <v-card class="pa-6" elevation="8">
+        <div class="text-center mb-6">
+          <v-img
+            class="mx-auto mb-6"
+            height="100"
+            src="@/assets/logo.png"
+            width="100"
+          />
+          <h1 class="text-h4 font-weight-bold mb-2">Welcome</h1>
+          <p class="text-body-1">Please sign in to continue</p>
+        </div>
+
+        <v-divider class="mb-6"></v-divider>
+
+        <v-btn
+          block
+          color="primary"
+          size="large"
+          @click="login"
+          prepend-icon="mdi-login"
+        >
+          Sign in with OAuth
+        </v-btn>
+      </v-card>
+    </v-responsive>
+  </v-container>
+</template>
+
+<script lang="ts" setup>
+const router = useRouter();
+
+function login() {
+  // Redirect to the OAuth login endpoint
+  window.location.href = '/oauth/login';
+}
+</script>
diff --git a/web/src/router/index.ts b/web/src/router/index.ts
index 5d4b8bc..6bdab2f 100644
--- a/web/src/router/index.ts
+++ b/web/src/router/index.ts
@@ -14,6 +14,22 @@ const router = createRouter({
   routes: setupLayouts(routes),
 })
 
+// Add authentication guard
+router.beforeEach((to, from, next) => {
+  // Check if the user is authenticated
+  const isAuthenticated = !!localStorage.getItem('auth_token');
+  
+  // If route requires auth and user is not authenticated, redirect to auth page
+  if (to.path !== '/auth' && !isAuthenticated) {
+    next('/auth');
+  } else if (to.path === '/auth' && isAuthenticated) {
+    // If user is already authenticated and tries to access auth page, redirect to home
+    next('/');
+  } else {
+    next();
+  }
+});
+
 // Workaround for https://github.com/vitejs/vite/issues/11804
 router.onError((err, to) => {
   if (err?.message?.includes?.('Failed to fetch dynamically imported module')) {
diff --git a/web/src/stores/auth.ts b/web/src/stores/auth.ts
new file mode 100644
index 0000000..f72ef7d
--- /dev/null
+++ b/web/src/stores/auth.ts
@@ -0,0 +1,53 @@
+// Auth store to manage authentication state
+import { defineStore } from 'pinia'
+
+export const useAuthStore = defineStore('auth', {
+  state: () => ({
+    token: localStorage.getItem('auth_token') || null,
+    user: null as any | null,
+  }),
+  
+  getters: {
+    isAuthenticated: (state) => !!state.token,
+  },
+  
+  actions: {
+    setToken(token: string) {
+      this.token = token;
+      localStorage.setItem('auth_token', token);
+    },
+    
+    setUser(user: any) {
+      this.user = user;
+    },
+    
+    logout() {
+      this.token = null;
+      this.user = null;
+      localStorage.removeItem('auth_token');
+    },
+    
+    async fetchUserInfo() {
+      if (!this.token) return;
+      
+      try {
+        const response = await fetch('/api/v1/health', {
+          headers: {
+            'Authorization': `Bearer ${this.token}`
+          }
+        });
+        
+        if (response.ok) {
+          const data = await response.json();
+          this.setUser(data.user);
+        } else {
+          // If token is invalid, logout
+          this.logout();
+        }
+      } catch (error) {
+        console.error('Failed to fetch user info:', error);
+        this.logout();
+      }
+    }
+  }
+})