better-auth-usos
v1.1.1
Published
USOS OAuth 1.0a authentication plugin for Better Auth
Maintainers
Readme
better-auth-usos
USOS OAuth 1.0a authentication plugin for Better Auth.
What is USOS?
USOS (University Study-Oriented System) is a comprehensive academic system used by universities across Poland and other countries. It manages student records, course registration, grades, schedules, and more.
Universities Using USOS
This plugin works with any USOS instance. Some notable universities include:
- Poland: Wrocław University of Science and Technology (PWR), University of Warsaw (UW), Warsaw University of Technology (PW), AGH University of Science and Technology, Jagiellonian University, and many more
- Other countries with USOS deployments
Each university has its own USOS instance with a unique base URL (e.g., https://apps.usos.pwr.edu.pl).
Features
- Full OAuth 1.0a flow implementation for USOS
- Automatic user creation and updates
- Configurable scopes and email domain
- TypeScript support with full type definitions
- Works with any USOS instance
- Both server-side and client-side plugins
Installation
npm install better-auth-usosPeer Dependencies
This plugin requires the following packages:
npm install better-auth crypto-js oauth-1.0a zodUsage
1. Get USOS API Credentials
To use this plugin, you need to register your application with your university's USOS system:
- Go to your university's USOS Apps page (e.g.,
https://apps.usos.pwr.edu.pl) - Navigate to "For developers" or "API" section
- Register a new application
- Set the callback URL to:
https://yourdomain.com/api/auth/usos/callback - Note your Consumer Key and Consumer Secret
2. Configure Environment Variables
Add these to your .env file:
USOS_BASE_URL=https://apps.usos.pwr.edu.pl
USOS_CONSUMER_KEY=your_consumer_key
USOS_CONSUMER_SECRET=your_consumer_secret
USOS_EMAIL_DOMAIN=student.pwr.edu.pl3. Database Schema & Custom Fields
The plugin allows you to map USOS profile data to your custom user fields using the userFields option:
import { betterAuth } from "better-auth";
import { usosAuth } from "better-auth-usos";
export const auth = betterAuth({
database: {
// your database config
},
user: {
additionalFields: {
studentNumber: {
type: "number",
required: false,
},
usosId: {
type: "string",
required: false,
},
firstName: {
type: "string",
required: false,
},
lastName: {
type: "string",
required: false,
},
},
},
plugins: [
usosAuth({
usosBaseUrl: process.env.USOS_BASE_URL!,
consumerKey: process.env.USOS_CONSUMER_KEY!,
consumerSecret: process.env.USOS_CONSUMER_SECRET!,
emailDomain: process.env.USOS_EMAIL_DOMAIN!,
scopes: "studies|offline_access",
userFields: (usosProfile) => ({
studentNumber: usosProfile.student_number
? Number.parseInt(usosProfile.student_number)
: null,
usosId: usosProfile.id,
firstName: usosProfile.first_name,
lastName: usosProfile.last_name,
}),
onSuccess: async (user) => {
console.log(user.firstName, user.lastName);
return "/dashboard";
},
}),
],
});The userFields function receives the USOS profile and returns an object with your custom fields. These fields will be:
- Added when creating a new user
- Updated when an existing user logs in
- Available in the
onSuccesscallback with full TypeScript support
After configuration, run Better Auth migrations to create the database schema:
npx better-auth migrate4. Client Setup
Add the client plugin to your Better Auth client:
import { createAuthClient } from "better-auth/client";
import { usosAuthClient } from "better-auth-usos/client";
export const authClient = createAuthClient({
plugins: [usosAuthClient()],
});5. Use in Your Application
Option 1: Using the client helper (Recommended)
import { authClient } from "./auth-client";
// React example
function LoginButton() {
const handleLogin = async () => {
await authClient.signIn.usos();
};
return <button onClick={handleLogin}>Login with USOS</button>;
}
// With options
function LoginWithOptions() {
const handleLogin = async () => {
await authClient.signIn.usos({
callbackURL: "/dashboard", // Custom redirect after login
newTab: false, // Open in new tab (optional)
});
};
return <button onClick={handleLogin}>Login with USOS</button>;
}Option 2: Direct redirect
function LoginButton() {
const handleLogin = () => {
window.location.href = "/api/auth/usos/login";
};
return <button onClick={handleLogin}>Login with USOS</button>;
}Configuration Options
UsosAuthPluginOptions
| Option | Type | Required | Default | Description |
| ---------------- | ------------------------------------------------------- | -------- | --------------------------- | ---------------------------------------------------------------------- |
| usosBaseUrl | string | Yes | - | Base URL of your USOS instance |
| consumerKey | string | Yes | - | OAuth consumer key from USOS |
| consumerSecret | string | Yes | - | OAuth consumer secret from USOS |
| emailDomain | string | Yes | - | Email domain for users without USOS email (e.g., student.pwr.edu.pl) |
| scopes | string | No | "studies\|offline_access" | OAuth scopes to request |
| userFields | (usosProfile: UsosUserProfile) => T | No | - | Function to map USOS profile to custom user fields |
| onSuccess | (user: UsosAuthUser & T) => Promise<string> \| string | No | "/" | Redirect path after successful auth |
Available Scopes
Common USOS scopes include:
studies- Access to student data (courses, grades, etc.)offline_access- Long-lived access tokensemail- Access to user's emailpersonal- Access to personal informationphoto- Access to user's photo
Combine multiple scopes with |: "studies|offline_access|email"
Check your USOS instance documentation for available scopes.
User Data
Default Fields
The plugin automatically stores these standard Better Auth fields:
email- If available from USOS, otherwise generated as{student_number}@{emailDomain}name- First name and last name combinedimage- Profile photo 50x50 (if available)emailVerified- Alwaystruefor USOS users
When a user doesn't have an email in USOS (common for students), the plugin generates one using their student number and the configured emailDomain. For example, if emailDomain is student.pwr.edu.pl and student number is 123456, the email will be [email protected].
Custom Fields with userFields
Use the userFields option to map any USOS profile data to your custom database fields. The function receives the full USOS profile:
interface UsosUserProfile {
id: string;
first_name: string;
last_name: string;
student_number: string | null;
email: string | null;
photo_urls?: {
"50x50"?: string;
"100x100"?: string;
};
}Example mapping common fields:
userFields: (usosProfile) => ({
studentNumber: usosProfile.student_number
? Number.parseInt(usosProfile.student_number)
: null,
usosId: usosProfile.id,
firstName: usosProfile.first_name,
lastName: usosProfile.last_name,
});These custom fields are automatically created/updated for every user login.
API Endpoints
The plugin creates these endpoints:
GET /api/auth/usos/login- Initiates OAuth flowGET /api/auth/usos/callback- Handles OAuth callback
TypeScript
Full TypeScript support is included. Import types as needed:
import type {
UsosAuthPluginOptions,
UsosAuthUser,
UsosUserProfile,
} from "better-auth-usos";Example Projects
Full Stack Example
// auth.ts (server)
import { betterAuth } from "better-auth";
import { usosAuth } from "better-auth-usos";
export const auth = betterAuth({
database: {
provider: "pg",
url: process.env.DATABASE_URL!,
},
user: {
additionalFields: {
studentNumber: {
type: "number",
required: false,
},
usosId: {
type: "string",
required: false,
},
firstName: {
type: "string",
required: false,
},
lastName: {
type: "string",
required: false,
},
},
},
plugins: [
usosAuth({
usosBaseUrl: process.env.USOS_BASE_URL!,
consumerKey: process.env.USOS_CONSUMER_KEY!,
consumerSecret: process.env.USOS_CONSUMER_SECRET!,
emailDomain: process.env.USOS_EMAIL_DOMAIN!,
userFields: (usosProfile) => ({
studentNumber: usosProfile.student_number
? Number.parseInt(usosProfile.student_number)
: null,
usosId: usosProfile.id,
firstName: usosProfile.first_name,
lastName: usosProfile.last_name,
}),
onSuccess: async (user) => {
console.log(`Welcome ${user.firstName} ${user.lastName}!`);
return "/dashboard";
},
}),
],
});
// auth-client.ts (client)
import { createAuthClient } from "better-auth/client";
import { usosAuthClient } from "better-auth-usos/client";
export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [usosAuthClient()],
});
// login-page.tsx
import { authClient } from "./auth-client";
function LoginPage() {
const handleLogin = async () => {
await authClient.signIn.usos();
};
return (
<div>
<h1>Login</h1>
<button onClick={handleLogin}>Login with USOS</button>
</div>
);
}Client API
The client plugin provides a convenient signIn.usos() method:
await authClient.signIn.usos(options);Options
| Option | Type | Default | Description |
| ------------- | --------- | ------- | ------------------------------------------ |
| callbackURL | string | - | Custom redirect URL after successful login |
| newTab | boolean | false | Open login in a new tab |
Examples
// Basic usage
await authClient.signIn.usos();
// With custom callback
await authClient.signIn.usos({
callbackURL: "/dashboard",
});
// Open in new tab
await authClient.signIn.usos({
newTab: true,
});Troubleshooting
"Failed to get request token"
- Verify your consumer key and secret are correct
- Check that your callback URL is registered in USOS
- Ensure the USOS base URL is correct
"Missing OAuth state"
- Check that cookies are enabled
- Verify your site is using HTTPS in production
- Check cookie settings (sameSite, secure)
"Invalid OAuth token"
- This usually indicates a CSRF attack or cookie issues
- Ensure cookies are properly configured
- Check that the user completes the OAuth flow within 10 minutes
License
MIT
Contributing
Contributions are welcome! Please open an issue or pull request on GitHub.
Support
- GitHub Issues: Report bugs or request features
- Better Auth Docs: https://www.better-auth.com
Credits
Created for the Polish university community using USOS systems.
