Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@docyrus/signin

Package Overview
Dependencies
Maintainers
4
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@docyrus/signin

Authentication provider for Docyrus apps (React, Vue, React Native, Electron, Next.js SSR)

latest
Source
npmnpm
Version
0.9.1
Version published
Maintainers
4
Created
Source

@docyrus/signin

Authentication provider for Docyrus apps. Provides "Sign in with Docyrus" experience with automatic environment detection — standalone apps use OAuth2 Authorization Code + PKCE via page redirect, while iframe-embedded apps receive tokens via window.postMessage from the host.

Supports React, Vue, React Native, Electron, and Next.js SSR via separate entrypoints.

Features

  • Auto Environment Detection: Detects standalone vs iframe mode automatically
  • OAuth2 PKCE: Full Authorization Code flow with PKCE (S256) for standalone apps
  • Iframe Auth: Receives tokens via postMessage from *.docyrus.app hosts
  • Token Auto-Refresh: Proactive token refresh before expiry in both modes (opt out with autoRefresh: false when an external system owns the token lifecycle)
  • Token Bootstrap: Start a session from existing access/refresh tokens (e.g. device flow handoff)
  • React Hooks: useDocyrusAuth() and useDocyrusClient() for easy integration
  • Vue Composables: useDocyrusAuth() and useDocyrusClient() for Vue 3
  • Authorization Helpers: hasRole() and hasPermission() for role-based access control
  • Electron Support: ElectronTokenStorage for IPC-based token storage + getAuthorizationUrl() for external browser login
  • Ready-to-Use API Client: Exposes a configured RestApiClient from @docyrus/api-client
  • SignInButton: Unstyled, customizable sign-in button component (React + Vue)
  • SSR Support: Sync access token to cookie for server-side rendering (Next.js) with max-age and Secure flag
  • Next.js SSR Helpers: createServerClient(), getSession(), authMiddleware() for server components, actions, and middleware
  • TypeScript: Full type definitions included

Installation

npm install @docyrus/signin
pnpm add @docyrus/signin

Peer Dependencies

  • @docyrus/api-client >= 0.0.4
  • react >= 19.2.0 (optional — required for React/Next.js entrypoint)
  • vue >= 3.5.0 (optional — required for Vue entrypoint)

Entrypoints

Import PathDescriptionFramework
@docyrus/signinReact provider, hooks, componentsReact, Next.js
@docyrus/signin/nextjsServer-side helpers (middleware, server client, session)Next.js
@docyrus/signin/vueVue provider, composables, componentsVue 3
@docyrus/signin/electronElectron token storageElectron
@docyrus/signin/react-nativeReact Native OAuth2 auth (in-app browser)React Native / Expo
@docyrus/signin/corePure TypeScript core (no framework dependency)Any

Quick Start (React)

import { DocyrusAuthProvider, useDocyrusAuth, useDocyrusClient, SignInButton } from '@docyrus/signin';

function App() {
  return (
    <DocyrusAuthProvider
      apiUrl="https://alpha-api.docyrus.com"
      clientId="your-oauth2-client-id"
      redirectUri="http://localhost:3000/callback"
      scopes={['offline_access', 'Read.All', 'Users.Read']}
      callbackPath="/callback">
      <Dashboard />
    </DocyrusAuthProvider>
  );
}

function Dashboard() {
  const { status, signOut } = useDocyrusAuth();
  const client = useDocyrusClient();

  if (status === 'loading') return <div>Loading...</div>;
  if (status === 'unauthenticated') return <SignInButton />;

  return (
    <div>
      <p>Authenticated!</p>
      <button onClick={() => client!.get('/v1/users/me').then(console.log)}>
        Fetch user
      </button>
      <button onClick={signOut}>Sign out</button>
    </div>
  );
}

Quick Start (Vue)

<!-- App.vue -->
<script setup lang="ts">
import { RouterView } from 'vue-router';
import { DocyrusAuthProvider } from '@docyrus/signin/vue';
</script>

<template>
  <DocyrusAuthProvider
    apiUrl="https://alpha-api.docyrus.com"
    clientId="your-oauth2-client-id"
    redirectUri="http://localhost:5173/callback"
    :scopes="['offline_access', 'Read.All', 'Users.Read']"
    callbackPath="/callback">
    <RouterView />
  </DocyrusAuthProvider>
</template>
<!-- Dashboard.vue -->
<script setup lang="ts">
import { useDocyrusAuth } from '@docyrus/signin/vue';

const { status, client, signIn, signOut } = useDocyrusAuth();
</script>

<template>
  <div v-if="status === 'loading'">Loading...</div>
  <button v-else-if="status === 'unauthenticated'" @click="signIn">Sign in</button>
  <div v-else>
    <p>Authenticated!</p>
    <button @click="signOut">Sign out</button>
  </div>
</template>

Quick Start (Electron)

import { DocyrusAuthProvider, useDocyrusAuth } from '@docyrus/signin';
import { ElectronTokenStorage } from '@docyrus/signin/electron';

const tokenStorage = new ElectronTokenStorage();

function App() {
  return (
    <DocyrusAuthProvider
      apiUrl="https://alpha-api.docyrus.com"
      clientId="your-oauth2-client-id"
      redirectUri="docyrus://callback"
      scopes={['offline_access', 'Read.All', 'Users.Read']}
      callbackPath="/callback"
      tokenStorage={tokenStorage}>
      <LoginPage />
    </DocyrusAuthProvider>
  );
}

function LoginPage() {
  const { status, getAuthorizationUrl } = useDocyrusAuth();

  const handleLogin = async () => {
    const url = await getAuthorizationUrl();
    if (url) {
      // Open in system browser instead of navigating
      await window.electronAPI.openExternal(url);
    }
  };

  return <button onClick={handleLogin} disabled={status === 'loading'}>Sign in</button>;
}

Quick Start (React Native / Expo)

import { DocyrusAuthProvider, useDocyrusAuth } from '@docyrus/signin/react-native';
import { createTokenStorage, ReactNativeCryptoProvider } from '@docyrus/api-client/react-native';
import * as WebBrowser from 'expo-web-browser';
import * as ExpoCrypto from 'expo-crypto';
import * as SecureStore from 'expo-secure-store';

const tokenStorage = createTokenStorage({
  getItem: (key) => SecureStore.getItemAsync(key),
  setItem: (key, value) => SecureStore.setItemAsync(key, value),
  removeItem: (key) => SecureStore.deleteItemAsync(key),
});

function App() {
  return (
    <DocyrusAuthProvider
      clientId="your-oauth2-client-id"
      forceMode="react-native"
      nativeRedirectUri="myapp://auth/callback"
      openAuthSession={WebBrowser.openAuthSessionAsync}
      tokenStorage={tokenStorage}
      cryptoProvider={new ReactNativeCryptoProvider(ExpoCrypto)}>
      <HomeScreen />
    </DocyrusAuthProvider>
  );
}

function HomeScreen() {
  const { status, signIn, signOut } = useDocyrusAuth();
  const client = useDocyrusClient();

  if (status === 'loading') return <Text>Loading...</Text>;
  if (status === 'unauthenticated') return <Button title="Sign In" onPress={signIn} />;

  return (
    <View>
      <Text>Authenticated!</Text>
      <Button title="Sign Out" onPress={signOut} />
    </View>
  );
}

React Native Props

PropTypeDescription
forceMode'react-native'Required — enables mobile OAuth2 flow
nativeRedirectUristringRequired — deep link URI scheme (e.g., myapp://auth/callback)
openAuthSession(url, redirectUri) => Promise<{type, url?}>RequiredWebBrowser.openAuthSessionAsync from expo-web-browser
tokenStorageOAuth2TokenStorageToken storage (use createTokenStorage() from api-client)
cryptoProviderCryptoProviderPKCE crypto (use ReactNativeCryptoProvider from api-client)

Configuration

Pass props to DocyrusAuthProvider to override defaults:

<DocyrusAuthProvider
  apiUrl="https://alpha-api.docyrus.com"
  clientId="your-oauth2-client-id"
  redirectUri="http://localhost:3000/callback"
  scopes={['offline_access', 'Read.All', 'Users.Read']}
  callbackPath="/callback"
  forceMode="standalone"
>
  <App />
</DocyrusAuthProvider>
PropTypeDefaultDescription
apiUrlstringhttps://alpha-api.docyrus.comDocyrus API base URL
clientIdstringRequiredOAuth2 client ID (throws if missing)
redirectUristringwindow.location.origin + callbackPathOAuth2 redirect URI
scopesstring[]['offline_access', 'Read.All', ...]OAuth2 scopes
callbackPathstring/auth/callbackPath to detect OAuth callback
forceMode'standalone' | 'iframe' | 'react-native'Auto-detectedForce a specific auth mode
nativeRedirectUristringDeep link URI for React Native OAuth2 callback
openAuthSessionFunctionWebBrowser.openAuthSessionAsync for React Native
cryptoProviderCryptoProviderBrowser cryptoCustom crypto for PKCE (React Native)
storageKeyPrefixstringdocyrus_oauth2_localStorage key prefix
tokenStorageOAuth2TokenStorageBrowser localStorageCustom token storage (e.g., ElectronTokenStorage)
initialTokensOAuth2TokensundefinedBootstrap a standalone / React Native session from existing tokens
autoRefreshbooleantrueWhen false, the provider never refreshes tokens itself (no pre-expiry timer, no pre-request refresh, no reactive 401 refresh). The current access token is used until replaced via signInWithTokens. Use when an external system owns the token lifecycle.
allowedHostOriginsstring[]undefinedExtra trusted iframe origins
ssrbooleanfalseSync access token to cookie for server components
ssrCookieKeystringdocyrus-tokenCookie name for SSR token sync

Hooks / Composables

useDocyrusAuth()

Returns the full authentication context. Available in both React and Vue (@docyrus/signin/vue).

const {
  status,              // 'loading' | 'authenticated' | 'unauthenticated'
  mode,                // 'standalone' | 'iframe'
  client,              // RestApiClient | null
  tokens,              // { accessToken, refreshToken, ... } | null
  user,                // DocyrusUser | null (auto-fetched from /v1/users/me)
  signIn,              // () => void (redirects to Docyrus login)
  signInWithTokens,    // (tokens) => Promise<void> (bootstrap from device flow / external auth)
  getAuthorizationUrl, // () => Promise<string | null> (returns URL without navigating — for Electron)
  signOut,             // () => Promise<void>
  hasRole,             // (role) => boolean — check if user has a role by slug or uid
  hasPermission,       // (operation, dataSourceId?) => boolean — check ACL permission
  refreshUser,         // () => Promise<void> — re-fetch user from API
  error                // Error | null
} = useDocyrusAuth();

useDocyrusClient()

Shorthand hook/composable that returns just the API client:

const client = useDocyrusClient(); // RestApiClient | null

if (client) {
  const response = await client.get('/v1/users/me');
}

Components

SignInButton

Unstyled button that triggers sign-in. Automatically hidden when authenticated or in iframe mode. Available in both React and Vue.

React:

// Basic
<SignInButton />

// With custom styling
<SignInButton className="btn btn-primary" label="Log in with Docyrus" />

// Render prop for full customization
<SignInButton>
  {({ signIn, isLoading }) => (
    <button onClick={signIn} disabled={isLoading}>
      {isLoading ? 'Redirecting...' : 'Sign in'}
    </button>
  )}
</SignInButton>

Vue:

<!-- Basic -->
<SignInButton />

<!-- With custom styling -->
<SignInButton label="Login" class="btn-primary" />

<!-- Scoped slot for custom rendering -->
<SignInButton v-slot="{ signIn, status }">
  <MyCustomButton @click="signIn" :loading="status === 'loading'" />
</SignInButton>

Electron Token Storage

ElectronTokenStorage stores OAuth2 tokens via IPC bridge to the Electron main process. Falls back to localStorage if the Electron API is not available.

import { ElectronTokenStorage } from '@docyrus/signin/electron';

// Auto-detects window.electronAPI
const storage = new ElectronTokenStorage();

// Or pass a custom IPC bridge
const storage = new ElectronTokenStorage(myElectronAPI);

// Custom key prefix
const storage = new ElectronTokenStorage(undefined, 'my_app_oauth2');

The Electron preload script must expose storeGet, storeSet, and storeDelete IPC methods via contextBridge.

Bootstrap From Existing Tokens

If you already have Docyrus accessToken and refreshToken from another flow such as device authorization, you can start a web session without redirecting through the browser login flow again.

The simplest option is initialTokens:

<DocyrusAuthProvider
  apiUrl="https://alpha-api.docyrus.com"
  clientId="your-oauth2-client-id"
  initialTokens={deviceFlowTokens}
  forceMode="standalone">
  <App />
</DocyrusAuthProvider>

Or bootstrap later with signInWithTokens():

import { useEffect } from 'react';
import { useDocyrusAuth } from '@docyrus/signin';

function TokenHandoff({ deviceFlowTokens }) {
  const { signInWithTokens } = useDocyrusAuth();

  useEffect(() => {
    if (!deviceFlowTokens) return;
    signInWithTokens(deviceFlowTokens).catch(console.error);
  }, [deviceFlowTokens, signInWithTokens]);

  return null;
}

@docyrus/signin will persist those tokens into its configured storage, create the authenticated API client, and continue normal refresh-token handling. In iframe mode this bootstrap path is intentionally unsupported; use the host postMessage signin flow instead.

Auth Modes

Standalone Mode (OAuth2 PKCE)

Used when the app runs directly in the browser (not in an iframe). The flow:

  • User clicks sign-in
  • Page redirects to Docyrus authorization endpoint
  • After login, redirects back with authorization code
  • Provider automatically exchanges code for tokens
  • Tokens stored in localStorage (or custom tokenStorage), auto-refreshed before expiry

Iframe Mode (postMessage)

Used when the app is embedded in an iframe on *.docyrus.app. The flow:

  • Provider detects iframe environment and validates host origin
  • Host sends { type: 'signin', accessToken, refreshToken } via postMessage
  • Provider creates API client with received tokens
  • When tokens expire, provider sends { type: 'token-refresh-request' } to host
  • Host responds with fresh tokens

SSR Support (Next.js)

Enable ssr on the client-side provider to sync the access token to a cookie. The cookie is written with max-age (matching the token lifetime) and Secure flag (on HTTPS origins) automatically.

1. Client — Enable SSR in AuthProvider

<DocyrusAuthProvider
  apiUrl="https://alpha-api.docyrus.com"
  clientId="your-oauth2-client-id"
  scopes={['offline_access', 'Read.All', 'Users.Read']}
  callbackPath="/auth/callback"
  ssr>
  {children}
</DocyrusAuthProvider>

The cookie is automatically set on login and token refresh, cleared on sign out, and expires with the token (via max-age).

2. Middleware — Route Protection

Use the pre-built authMiddleware for automatic route protection:

// middleware.ts
import { authMiddleware } from '@docyrus/signin/nextjs';

export default authMiddleware({
  publicRoutes: ['/login', '/callback'],
  loginPath: '/login',
});

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\..*).*)'],
};

Or use getMiddlewareSession for custom middleware logic:

// middleware.ts
import { NextResponse, type NextRequest } from 'next/server';
import { getMiddlewareSession } from '@docyrus/signin/nextjs';

export function middleware(request: NextRequest) {
  const session = getMiddlewareSession(request);
  if (!session.isAuthenticated && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  return NextResponse.next();
}

3. Server Components — Authenticated API Calls

import { cookies } from 'next/headers';
import { createServerClient, getSession } from '@docyrus/signin/nextjs';
import { redirect } from 'next/navigation';

export default async function DashboardPage() {
  const cookieStore = await cookies();
  const session = getSession(cookieStore);
  if (!session.isAuthenticated) redirect('/login');

  const client = createServerClient(cookieStore);
  const { data: user } = await client.get('/v1/users/me');
  return <div>Welcome, {user.name}</div>;
}

4. Server Actions

'use server';
import { cookies } from 'next/headers';
import { createServerClient } from '@docyrus/signin/nextjs';

export async function getUser() {
  const client = createServerClient(await cookies());
  return client.get('/v1/users/me');
}

Next.js SSR API Reference

FunctionImportUsage
getSession(cookies)@docyrus/signin/nextjsRead auth session in server components/actions
createServerClient(cookies)@docyrus/signin/nextjsCreate authenticated RestApiClient on server
authMiddleware(config)@docyrus/signin/nextjsPre-built route protection middleware
getMiddlewareSession(request)@docyrus/signin/nextjsRead auth session in custom middleware
createMiddlewareClient(request)@docyrus/signin/nextjsCreate authenticated client in middleware
AttributeValueReason
path/Available to all routes
SameSiteLaxSent on same-site navigations
SecureAuto (https: only)Only sent over HTTPS in production
max-ageToken lifetime (seconds)Expires with the token, survives browser restart
httpOnlyNoClient-side auth provider must read/write it
import { getTokenFromCookie } from '@docyrus/signin';

const token = getTokenFromCookie(); // reads 'docyrus-token' cookie
const token = getTokenFromCookie('custom-key'); // custom cookie name

Authorization (Roles & Permissions)

The provider auto-fetches the current user from /v1/users/me after authentication and exposes hasRole and hasPermission helpers.

Via Hooks (React / React Native)

function DataSourceView({ dataSourceId }: { dataSourceId: string }) {
  const { user, hasRole, hasPermission } = useDocyrusAuth();

  if (!user) return <div>Loading user...</div>;

  // Check role by slug or uid
  if (hasRole('super_admin')) {
    return <AdminPanel />;
  }

  // Check data source permission
  const canEdit = hasPermission('edit', dataSourceId);
  const canDelete = hasPermission('delete', dataSourceId);

  // Check multiple roles
  if (hasRole(['editor', 'reviewer'])) {
    return <EditorView canEdit={canEdit} canDelete={canDelete} />;
  }

  return <ReadOnlyView />;
}

Via Pure Functions (Framework-Agnostic)

import { hasRole, hasPermission } from '@docyrus/signin/core';
import type { DocyrusUser } from '@docyrus/signin/core';

function checkAccess(user: DocyrusUser) {
  // hasRole returns true when role arg is null/undefined (no requirement)
  hasRole(user, null);              // true
  hasRole(user, 'super_admin');     // checks slug or uid
  hasRole(user, ['editor', 'viewer']); // any match

  // hasPermission checks: super_admin → global_editor → global_viewer → always-permitted → aclRules
  hasPermission(user, 'view', 'some-ds-id');
  hasPermission(user, 'edit', 'some-ds-id');
}

Permission Resolution Order

  • super_admin role → always granted
  • global_editor role → granted for: view, create, edit, delete, create_bulk, export, import, print
  • global_viewer role → granted only for: view
  • Always-permitted system data sources (reports, todos, notes, etc.)
  • User's aclRules array (merged from all roles by the server)

Refreshing User Data

If roles or permissions change while the app is running:

const { refreshUser } = useDocyrusAuth();

// After a role change
await refreshUser();

Advanced Usage

Core (Framework-Agnostic)

Core classes are exported for advanced use cases without any framework dependency:

import {
  AuthManager,
  StandaloneOAuth2Auth,
  IframeAuth,
  detectAuthMode,
} from '@docyrus/signin/core';

React Entrypoint

import {
  DocyrusAuthProvider,
  useDocyrusAuth,
  useDocyrusClient,
  SignInButton,
  AuthManager,
  detectAuthMode
} from '@docyrus/signin';

Vue Entrypoint

import {
  DocyrusAuthProvider,
  useDocyrusAuth,
  useDocyrusClient,
  SignInButton
} from '@docyrus/signin/vue';

Development

# Install dependencies
pnpm install

# Development mode with watch
pnpm dev

# Build the package
pnpm build

# Run linting
pnpm lint

# Type checking
pnpm typecheck

License

MIT

FAQs

Package last updated on 21 May 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts