---
title: "Login Flow"
description: "Build a login page, wire up logout, and update the navigation layout to conditionally show auth-related links using useUserSession."
canonical_url: "https://vercel.com/academy/nuxt-on-vercel/login-flow"
md_url: "https://vercel.com/academy/nuxt-on-vercel/login-flow.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-10-16T17:45:50.251Z"
content_type: "lesson"
course: "nuxt-on-vercel"
course_title: "Nuxt on Vercel"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Login Flow

# Login Flow

The OAuth handler can authenticate users, but the interface still needs login and logout controls plus session-aware navigation.

Next.js auth libraries expose hooks such as `useSession` or `useUser` for conditional UI. Nuxt's `useUserSession` returns the login state, user data, and a `clear` function for logout.

## Outcome

Build a login page, add logout functionality, and update the nav to reflect auth state.

## Fast Track

1. Build the login page with a "Sign in with GitHub" link
2. Add `useUserSession` to the layout and conditionally show auth links
3. Wire up the logout button

## Hands-on exercise 3.2

Build the user-facing auth experience.

**Requirements:**

1. Update `app/pages/login.vue` with a GitHub login link and error handling
2. Redirect already-logged-in users away from the login page
3. Update `app/layouts/default.vue` to show Favorites and Visited links when logged in
4. Show the user's GitHub username and a logout button when logged in
5. Show a "Log in" link when logged out

**Implementation hints:**

- `useUserSession()` returns `{ loggedIn, user, clear }`. `loggedIn` is a boolean ref, `user` is the session data from your OAuth handler, `clear` logs the user out
- The login link should be a plain `<a href="/auth/github">`, not a `NuxtLink`. It triggers a server redirect, not client-side navigation
- `navigateTo("/springs")` is Nuxt's programmatic navigation. Use it for redirects in `<script setup>`
- `v-if` / `v-else` on the template handles conditional rendering

The login page needs a heading, error state, and link that starts the OAuth flow:

```vue title="app/pages/login.vue"
<script setup lang="ts">
const { loggedIn } = useUserSession();

// Redirect if already logged in
if (loggedIn.value) {
  navigateTo("/springs");
}

const route = useRoute();
const hasError = computed(() => route.query.error === "auth");
</script>

<template>
  <div>
    <h1>Log In</h1>
    <p>
      Sign in with GitHub to save favorites, track visited springs, and leave
      reviews.
    </p>

    <p v-if="hasError">
      Something went wrong with authentication. Please try again.
    </p>

    <a href="/auth/github">
      Sign in with GitHub
    </a>
  </div>
</template>
```

The check at the top redirects authenticated users to `/springs` from `<script setup>`.

Use a regular `<a>` tag for the login link because `/auth/github` is a server route that redirects to GitHub. `NuxtLink` would attempt client-side navigation instead.

The error state uses `route.query.error` to check if the OAuth handler redirected back with `?error=auth`. Remember our handler from the last lesson: `sendRedirect(event, "/login?error=auth")`. This is where that query parameter gets consumed.

Now let's update the layout to show auth state in the nav:

```vue title="app/layouts/default.vue"
<script setup lang="ts">
const { loggedIn, user, clear } = useUserSession();
</script>

<template>
  <div>
    <header>
      <nav>
        <NuxtLink to="/">
          Hot Springs Finder
        </NuxtLink>
        <div>
          <NuxtLink to="/springs">
            Browse
          </NuxtLink>
          <template v-if="loggedIn">
            <NuxtLink to="/favorites">
              Favorites
            </NuxtLink>
            <NuxtLink to="/visited">
              Visited
            </NuxtLink>
            <div>
              <span>{{ user?.login }}</span>
              <button @click="clear">
                Log out
              </button>
            </div>
          </template>
          <NuxtLink v-else to="/login">
            Log in
          </NuxtLink>
        </div>
      </nav>
    </header>

    <main>
      <slot />
    </main>

    <footer>
      Hot Springs Finder &mdash; Built with Nuxt
    </footer>
  </div>
</template>
```

The `<template v-if="loggedIn">` block is a Vue pattern for grouping multiple elements under one condition without adding an extra DOM node. In React, you'd use a fragment: `{loggedIn && <>...</>}`. Vue's `<template>` with a directive serves the same purpose.

The `clear` function removes the session and its cookie. `useUserSession` updates the reactive auth state after logout.

\*\*Note: useUserSession works everywhere\*\*

Any component can call `useUserSession()` and read the same session cookie. For example, a detail page can check whether the user may favorite a spring.

\*\*Warning: Session data is what you stored\*\*

`user` only contains the fields you passed to `setUserSession` in the OAuth handler. If you need the user's email or full name, add those fields in the handler. You can't access them later without re-fetching from GitHub.

## Try It

1. Make sure you're logged out (clear cookies or click "Log out" if available)
2. Visit `http://localhost:3000`. You should see "Browse" and "Log in" in the nav
3. Click "Log in." You'll see the login page with a "Sign in with GitHub" button
4. Click it. Authorize with GitHub. You should land on `/springs`
5. The nav should now show: Browse, Favorites, Visited, your GitHub username, and "Log out"
6. Click "Log out." The nav reverts to showing only "Browse" and "Log in"
7. Visit `http://localhost:3000/login` while logged in. You should be redirected to `/springs`

## Commit

```bash
git add -A && git commit -m "feat(auth): add login page and auth-aware navigation"
```

## Done-When

- [ ] The login page shows a "Sign in with GitHub" button
- [ ] Error state appears when redirected with `?error=auth`
- [ ] The nav shows Favorites, Visited, username, and Log out when authenticated
- [ ] The nav shows only Browse and Log in when unauthenticated
- [ ] Logging out clears the session and updates the nav immediately

## Solution

```vue title="app/pages/login.vue"
<script setup lang="ts">
const { loggedIn } = useUserSession();

if (loggedIn.value) {
  navigateTo("/springs");
}

const route = useRoute();
const hasError = computed(() => route.query.error === "auth");
</script>

<template>
  <div>
    <h1>Log In</h1>
    <p>
      Sign in with GitHub to save favorites, track visited springs, and leave
      reviews.
    </p>

    <p v-if="hasError">
      Something went wrong with authentication. Please try again.
    </p>

    <a href="/auth/github">
      Sign in with GitHub
    </a>
  </div>
</template>
```

```vue title="app/layouts/default.vue"
<script setup lang="ts">
const { loggedIn, user, clear } = useUserSession();
</script>

<template>
  <div>
    <header>
      <nav>
        <NuxtLink to="/">
          Hot Springs Finder
        </NuxtLink>
        <div>
          <NuxtLink to="/springs">
            Browse
          </NuxtLink>
          <template v-if="loggedIn">
            <NuxtLink to="/favorites">
              Favorites
            </NuxtLink>
            <NuxtLink to="/visited">
              Visited
            </NuxtLink>
            <div>
              <span>{{ user?.login }}</span>
              <button @click="clear">
                Log out
              </button>
            </div>
          </template>
          <NuxtLink v-else to="/login">
            Log in
          </NuxtLink>
        </div>
      </nav>
    </header>

    <main>
      <slot />
    </main>

    <footer>
      Hot Springs Finder &mdash; Built with Nuxt
    </footer>
  </div>
</template>
```


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
