
Research
/Security News
Laravel Lang Compromised with RCE Backdoor Across 700+ Versions
Laravel Lang packages were compromised with an RCE backdoor across hundreds of versions, exposing cloud, CI/CD, and developer secrets.
@docyrus/signin
Advanced tools
Authentication provider for Docyrus apps (React, Vue, React Native, Electron, Next.js SSR)
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.
postMessage from *.docyrus.app hostsautoRefresh: false when an external system owns the token lifecycle)useDocyrusAuth() and useDocyrusClient() for easy integrationuseDocyrusAuth() and useDocyrusClient() for Vue 3hasRole() and hasPermission() for role-based access controlElectronTokenStorage for IPC-based token storage + getAuthorizationUrl() for external browser loginRestApiClient from @docyrus/api-clientmax-age and Secure flagcreateServerClient(), getSession(), authMiddleware() for server components, actions, and middlewarenpm install @docyrus/signin
pnpm add @docyrus/signin
@docyrus/api-client >= 0.0.4react >= 19.2.0 (optional — required for React/Next.js entrypoint)vue >= 3.5.0 (optional — required for Vue entrypoint)| Import Path | Description | Framework |
|---|---|---|
@docyrus/signin | React provider, hooks, components | React, Next.js |
@docyrus/signin/nextjs | Server-side helpers (middleware, server client, session) | Next.js |
@docyrus/signin/vue | Vue provider, composables, components | Vue 3 |
@docyrus/signin/electron | Electron token storage | Electron |
@docyrus/signin/react-native | React Native OAuth2 auth (in-app browser) | React Native / Expo |
@docyrus/signin/core | Pure TypeScript core (no framework dependency) | Any |
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>
);
}
<!-- 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>
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>;
}
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>
);
}
| Prop | Type | Description |
|---|---|---|
forceMode | 'react-native' | Required — enables mobile OAuth2 flow |
nativeRedirectUri | string | Required — deep link URI scheme (e.g., myapp://auth/callback) |
openAuthSession | (url, redirectUri) => Promise<{type, url?}> | Required — WebBrowser.openAuthSessionAsync from expo-web-browser |
tokenStorage | OAuth2TokenStorage | Token storage (use createTokenStorage() from api-client) |
cryptoProvider | CryptoProvider | PKCE crypto (use ReactNativeCryptoProvider from api-client) |
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>
| Prop | Type | Default | Description |
|---|---|---|---|
apiUrl | string | https://alpha-api.docyrus.com | Docyrus API base URL |
clientId | string | Required | OAuth2 client ID (throws if missing) |
redirectUri | string | window.location.origin + callbackPath | OAuth2 redirect URI |
scopes | string[] | ['offline_access', 'Read.All', ...] | OAuth2 scopes |
callbackPath | string | /auth/callback | Path to detect OAuth callback |
forceMode | 'standalone' | 'iframe' | 'react-native' | Auto-detected | Force a specific auth mode |
nativeRedirectUri | string | — | Deep link URI for React Native OAuth2 callback |
openAuthSession | Function | — | WebBrowser.openAuthSessionAsync for React Native |
cryptoProvider | CryptoProvider | Browser crypto | Custom crypto for PKCE (React Native) |
storageKeyPrefix | string | docyrus_oauth2_ | localStorage key prefix |
tokenStorage | OAuth2TokenStorage | Browser localStorage | Custom token storage (e.g., ElectronTokenStorage) |
initialTokens | OAuth2Tokens | undefined | Bootstrap a standalone / React Native session from existing tokens |
autoRefresh | boolean | true | When 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. |
allowedHostOrigins | string[] | undefined | Extra trusted iframe origins |
ssr | boolean | false | Sync access token to cookie for server components |
ssrCookieKey | string | docyrus-token | Cookie name for SSR token sync |
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();
Shorthand hook/composable that returns just the API client:
const client = useDocyrusClient(); // RestApiClient | null
if (client) {
const response = await client.get('/v1/users/me');
}
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>
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.
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.
Used when the app runs directly in the browser (not in an iframe). The flow:
tokenStorage), auto-refreshed before expiryUsed when the app is embedded in an iframe on *.docyrus.app. The flow:
{ type: 'signin', accessToken, refreshToken } via postMessage{ type: 'token-refresh-request' } to hostEnable 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.
<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).
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();
}
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>;
}
'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');
}
| Function | Import | Usage |
|---|---|---|
getSession(cookies) | @docyrus/signin/nextjs | Read auth session in server components/actions |
createServerClient(cookies) | @docyrus/signin/nextjs | Create authenticated RestApiClient on server |
authMiddleware(config) | @docyrus/signin/nextjs | Pre-built route protection middleware |
getMiddlewareSession(request) | @docyrus/signin/nextjs | Read auth session in custom middleware |
createMiddlewareClient(request) | @docyrus/signin/nextjs | Create authenticated client in middleware |
| Attribute | Value | Reason |
|---|---|---|
path | / | Available to all routes |
SameSite | Lax | Sent on same-site navigations |
Secure | Auto (https: only) | Only sent over HTTPS in production |
max-age | Token lifetime (seconds) | Expires with the token, survives browser restart |
httpOnly | No | Client-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
The provider auto-fetches the current user from /v1/users/me after authentication and exposes hasRole and hasPermission helpers.
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 />;
}
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');
}
super_admin role → always grantedglobal_editor role → granted for: view, create, edit, delete, create_bulk, export, import, printglobal_viewer role → granted only for: viewaclRules array (merged from all roles by the server)If roles or permissions change while the app is running:
const { refreshUser } = useDocyrusAuth();
// After a role change
await refreshUser();
Core classes are exported for advanced use cases without any framework dependency:
import {
AuthManager,
StandaloneOAuth2Auth,
IframeAuth,
detectAuthMode,
} from '@docyrus/signin/core';
import {
DocyrusAuthProvider,
useDocyrusAuth,
useDocyrusClient,
SignInButton,
AuthManager,
detectAuthMode
} from '@docyrus/signin';
import {
DocyrusAuthProvider,
useDocyrusAuth,
useDocyrusClient,
SignInButton
} from '@docyrus/signin/vue';
# Install dependencies
pnpm install
# Development mode with watch
pnpm dev
# Build the package
pnpm build
# Run linting
pnpm lint
# Type checking
pnpm typecheck
MIT
FAQs
Authentication provider for Docyrus apps (React, Vue, React Native, Electron, Next.js SSR)
The npm package @docyrus/signin receives a total of 260 weekly downloads. As such, @docyrus/signin popularity was classified as not popular.
We found that @docyrus/signin demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?

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.

Research
/Security News
Laravel Lang packages were compromised with an RCE backdoor across hundreds of versions, exposing cloud, CI/CD, and developer secrets.

Security News
Socket found a malicious postinstall hook across 700+ GitHub repos, including PHP packages on Packagist and Node.js project repositories.

Security News
Vibe coding at scale is reshaping how packages are created, contributed, and selected across the software supply chain