Thoryn

Quickstarts · vue

Vue 3 — Hub login with `oidc-client-ts`

Wire OAuth2 / OIDC into a Vue 3 + Vite app. Auth code with PKCE, composable for the rest of the app.

Tested against:framework: Vue 3.4 + Vite 5oidcClient: oidc-client-ts@3.0.0

Vue + Thoryn quickstart architecture — composable + Vue Router guard route through Hub to your federation member with PKCE; access token returns to the SPA

Prereqs

  • Vue 3 + Vite 5
  • A Thoryn account

Step 1 — Register a SPA client

hub clients create \
  --name "My Vue app" \
  --redirect-uri "http://localhost:5173/callback" \
  --grant-types authorization_code,refresh_token \
  --client-type public \
  --pkce required \
  --scopes "openid email profile"

Step 2 — Install

npm install oidc-client-ts

Step 3 — Composable

src/composables/useAuth.ts:

import { ref } from "vue";
import { UserManager, WebStorageStateStore, type User } from "oidc-client-ts";
 
const userManager = new UserManager({
  authority: "https://hub.thoryn.org",
  client_id: import.meta.env.VITE_THORYN_CLIENT_ID,
  redirect_uri: window.location.origin + "/callback",
  scope: "openid email profile",
  response_type: "code",
  userStore: new WebStorageStateStore({ store: window.localStorage }),
});
 
const user = ref<User | null>(null);
 
export function useAuth() {
  return {
    user,
    signIn: () => userManager.signinRedirect(),
    signOut: () => userManager.signoutRedirect(),
    handleCallback: async () => {
      user.value = await userManager.signinRedirectCallback();
    },
    refresh: async () => {
      user.value = await userManager.getUser();
    },
  };
}

Step 4 — Wire it into App.vue

<script setup>
import { onMounted } from "vue";
import { useAuth } from "./composables/useAuth";
const { user, signIn, signOut, handleCallback, refresh } = useAuth();
 
onMounted(async () => {
  if (window.location.pathname === "/callback") {
    await handleCallback();
    window.history.replaceState({}, "", "/");
  } else {
    await refresh();
  }
});
</script>
 
<template>
  <button v-if="!user" @click="signIn">Sign in</button>
  <div v-else>
    <p>Hello, {{ user.profile.name }}</p>
    <button @click="signOut">Sign out</button>
  </div>
</template>

Step 5 — Run it

VITE_THORYN_CLIENT_ID=... npm run dev

What's next

Troubleshooting

  • Reactivity quirks: oidc-client-ts returns plain objects. Wrap them in ref if you need deep reactivity.