Skip to main content

1. Overview

FieldValue
Module NameAuthentication
Module CodeAUTH
ProductSKOLE (All surfaces)
Version1.0
StatusActive
Business ValueSecures all platform data behind role-appropriate authentication. Each user type (parent, teacher/staff, admin) has a dedicated auth flow. Session tokens with FCM device registration ensure push-notification delivery.

Scope In

  • Parent login via phone number + PIN
  • Teacher/Staff login via phone + PIN
  • Web Admin login via email + password
  • Session/device management
  • Logout for all roles
  • PIN setup for first-time parents

Scope Out

  • OAuth / Social login
  • Two-factor authentication (schema reserved, not active)
  • Password reset via OTP (available endpoint, not wired to SMS)

2. Requirements

Functional Requirements (FR)

What the module must DO — actions, behaviors, and outcomes.
  • SKOLE-AUTH-FR001: The module shall allow parents to authenticate using their registered mobile number and a PIN.
  • SKOLE-AUTH-FR002: The module shall match both father and mother phone numbers against parent_details.
  • SKOLE-AUTH-FR003: The module shall return all linked children’s profiles if the PIN matches.
  • SKOLE-AUTH-FR004: The module shall create a parent_devices record with device info and FCM token on login.
  • SKOLE-AUTH-FR005: The module shall generate and return a JWT containing sub, type, skole_id, phone, and session_token.
  • SKOLE-AUTH-FR006: The module shall allow teachers to authenticate with phone number and PIN against the staff table.
  • SKOLE-AUTH-FR007: The module shall create a staff_sessions record on teacher login.
  • SKOLE-AUTH-FR008: The module shall allow web admins to sign up with name, email, phone, and password.
  • SKOLE-AUTH-FR009: The module shall allow web admins to sign in with email and password.
  • SKOLE-AUTH-FR010: The module shall invalidate sessions on logout by marking the record as inactive (is_active = 0).
  • SKOLE-AUTH-FR011: The module shall allow updating device info (FCM token, platform, model) post-login.
  • SKOLE-AUTH-FR012: The module shall provide a PIN setup flow for first-time parent users.
  • SKOLE-AUTH-FR013: The module shall allow staff to set or edit passwords separately from the parent flow.

Non-Functional Requirements (NFR)

How well the module must do it — performance, security, and reliability.
  • SKOLE-AUTH-NFR001: The module shall perform authentication checks ensuring JWT tokens are short-lived and sessions expire after 30 days.
  • SKOLE-AUTH-NFR002: The module shall behave securely by ensuring session tokens are unique per device.
  • SKOLE-AUTH-NFR003: The module shall behave as a secure gateway by protecting all non-auth routes with a global JwtAuthGuard.
  • SKOLE-AUTH-NFR004: The module shall perform bypass logic for auth routes decorated with @Public().
  • SKOLE-AUTH-NFR005: The module shall support JWT extraction via Authorization header, query parameter, or cookie.

Constraints

Rules and boundaries — tech choices and platform restrictions.
  • C001: We must use PostgreSQL (via Prisma) for persisting users and sessions because it is the platform’s primary data store.
  • C002: We must use Firebase FCM for push notification registration to ensure cross-platform delivery.
  • C003: We must use standard JWT (jsonwebtoken) for token signing because the architecture requires stateless authentication.

3. Sub-modules / Backlog

IDSub-modulePriorityStatusEstimateLinked Requirement
SKOLE-AUTH-SM001Parent PIN AuthenticationP0Done3dFR001–FR005
SKOLE-AUTH-SM002Teacher PIN AuthenticationP0Done2dFR006–FR007
SKOLE-AUTH-SM003Web Admin Signup / SigninP0Done2dFR008–FR009
SKOLE-AUTH-SM004Session & Device ManagementP1Done2dFR010–FR011
SKOLE-AUTH-SM005PIN Setup for New ParentsP1Done1dFR012
SKOLE-AUTH-SM006Staff Password ManagementP1Done1dFR013

4. Logical Implementation

Parent Authentication Flow

POST /parent-app/parent-auth-verification
  ├─ Input: { phone_no, pin, fcm_token, device_info }
  ├─ Step 1: Query parent_details WHERE father_phone = phone OR mother_phone = phone
  │    └─ If no record → 401 "Invalid phone number"
  ├─ Step 2: Filter records WHERE pin matches
  │    └─ If no match → 401 "Invalid PIN"
  ├─ Step 3: Collect validParentRecords = pin matches + null-PIN siblings
  ├─ Step 4: Query students table for all linked children
  ├─ Step 5: Generate session_token (UUID) + expiresAt (now + 30 days)
  ├─ Step 6: Insert parent_devices record
  ├─ Step 7: Update parent_details.fcm_token
  ├─ Step 8: Sign JWT payload → return access_token + parent profile + children[]

Teacher Authentication Flow

POST /teachers-app/teacher-auth-verification
  ├─ Input: { phone_no, pin, fcm_token, device_info }
  ├─ Step 1: Query staff WHERE phone_no = input AND skole_id = input.skole_id
  │    └─ If no record → 401
  ├─ Step 2: Verify PIN match
  ├─ Step 3: Insert staff_sessions record
  ├─ Step 4: Sign JWT with type='staff'
  └─ Return access_token + staff profile

Web Admin Signin Flow

POST /web-app/signin
  ├─ Input: { email, password }
  ├─ Step 1: Query users WHERE email = input
  │    └─ If not found → 401
  ├─ Step 2: bcrypt.compare(password, user.password)
  ├─ Step 3: Insert user_sessions record
  ├─ Step 4: Sign JWT with type='user'
  └─ Return access_token + user profile

JWT Guard Strategy

All API routes are protected globally. The @Public() decorator (custom metadata key isPublic) bypasses the guard. JWT is extracted from:
  1. Authorization: Bearer <token> header
  2. ?access_token=<token> query parameter
  3. Cookie access_token

Error Handling

ScenarioHTTP CodeMessage
Phone not found401”Invalid phone number”
Wrong PIN401”Invalid PIN”
Expired session401”Unauthorized”
Missing roll_no / skole_id400Field-specific message

5. UI Requirements

Parent App — Auth Screens

IDScreenRouteDescription
SKOLE-AUTH-UI001PinLoginScreen/pin-loginPhone number and 4-digit PIN entry for parents
SKOLE-AUTH-UI002ChildSelectionScreen/child-selectionScreen to select which child to view (for multi-child parents)

Teacher App — Auth Screens

IDScreenRouteDescription
SKOLE-AUTH-UI006LoginScreen/loginPhone + PIN single-page login for staff

Web App — Auth Screen

IDScreenRouteDescription
SKOLE-AUTH-UI007Login Page/loginEmail + password login for school admins

UI Components

IDComponentPropsUsed In
SKOLE-AUTH-UC001PhoneInputFieldvalue, onChange, placeholderUI001, UI002, UI006
SKOLE-AUTH-UC002PinInputlength=4, onComplete(pin)UI003, UI004
SKOLE-AUTH-UC003AuthButtonlabel, onPress, loadingAll auth screens
SKOLE-AUTH-UC004SchoolLogoskole_idUI001, UI006

UI States

ScreenLoadingEmpty / First TimeError
PinLoginScreenSpinner overlay on submitRedirect to PinSetup if no PIN set”Invalid PIN” toast
LoginScreenDisabled button + spinner“Invalid phone number” toast
Web LoginButton spinnerInline error message

6. Conditional Expressions

IDExpressionTriggerTrue ActionFalse Action
SKOLE-AUTH-CE001allParentRecords.length === 0POST parent-auth-verificationReturn 401 “Invalid phone number”Proceed to PIN check
SKOLE-AUTH-CE002pinMatches.length === 0After phone lookupReturn 401 “Invalid PIN”Build validParentRecords
SKOLE-AUTH-CE003p.pin === nullBuilding validParentRecordsInclude sibling records (null PIN)Exclude
SKOLE-AUTH-CE004@Public() decorator presentEvery API requestSkip JwtAuthGuardEnforce JWT check
SKOLE-AUTH-CE005user.pin === null (parent app)PinLoginScreen renderRedirect to PinSetupScreenShow PIN entry
SKOLE-AUTH-CE006jwt.type === 'parent'JWT validation in guardAttach parent contextCheck other type
SKOLE-AUTH-CE007jwt.type === 'staff'JWT validationAttach staff contextCheck other type
SKOLE-AUTH-CE008session.is_active === 0Any authenticated request401 UnauthorizedAllow request

7. Internal Module Connections

DirectionModuleData / EventCondition
AUTH → STUDStudent ManagementChildren list returned on loginAlways on successful parent auth
AUTH → PROFProfileParent profile data embedded in auth responseAlways
AUTH → NOTFNotificationsFCM token stored in parent_devices / staff recordOn login / device update
STUD → AUTHStudent Managementroll_no, skole_id used to scope authAlways
All modules → AUTHEvery moduleJWT session_token, skole_id, sub used in every requestAll protected routes

8. External Connections

IDServicePurposeAuth MethodFailure Behavior
SKOLE-AUTH-EX001PostgreSQL (via Prisma)Persist users, sessions, parent_detailsDB credentials via DATABASE_URL env500 Internal Server Error
SKOLE-AUTH-EX002Firebase FCMPush notification token registrationFirebase Admin SDK (server key)Auth succeeds, notifications silently fail
SKOLE-AUTH-EX003JWT (jsonwebtoken)Token signing / verificationJWT_SECRET env variable401 Unauthorized

9. Database Tables

IDTableRole
SKOLE-AUTH-TB001usersWeb admin accounts
SKOLE-AUTH-TB002parent_detailsParent phone, PIN, email, FCM token
SKOLE-AUTH-TB003parent_devicesDevice sessions for parents (unique session_token)
SKOLE-AUTH-TB004staffStaff records including PIN and FCM token
SKOLE-AUTH-TB005staff_sessionsDevice sessions for staff (unique session_token)
SKOLE-AUTH-TB006user_sessionsDevice sessions for web admins

Key Relationships

  • parent_devices.parent_idparent_details.id
  • parent_details.student_roll_no + parent_details.skole_idstudents.roll_no + students.skole_id
  • staff_sessions.staff_idstaff.id
  • user_sessions.user_idusers.id

10. API Endpoints Summary

IDMethodRouteAuthDescription
SKOLE-AUTH-EP001POST/parent-app/parent-auth-verificationPublicParent phone+PIN login
SKOLE-AUTH-EP002POST/parent-app/logoutJWT (parent)Invalidate parent session
SKOLE-AUTH-EP003PUT/parent-app/auth/device-infoJWT (parent)Update device/FCM info
SKOLE-AUTH-EP004POST/teachers-app/teacher-auth-verificationPublicTeacher phone+PIN login
SKOLE-AUTH-EP005POST/teachers-app/logoutJWT (staff)Invalidate staff session
SKOLE-AUTH-EP006POST/web-app/signupPublicCreate admin account
SKOLE-AUTH-EP007POST/web-app/signinPublicAdmin email+password login
SKOLE-AUTH-EP008POST/web-app/reset-passwordPublicReset admin password
SKOLE-AUTH-EP009POST/web-app/parent-set-passwordPublicSet parent PIN (first time)
SKOLE-AUTH-EP010PATCH/web-app/parent-edit-passwordPublicEdit parent PIN
SKOLE-AUTH-EP011POST/web-app/staff-set-passwordPublicSet staff PIN (first time)
SKOLE-AUTH-EP012PATCH/web-app/staff-edit-passwordPublicEdit staff PIN
SKOLE-AUTH-EP013POST/web-app/logoutJWT (user)Invalidate admin session

  • Old devices can be logged out via separate endpoint

EP001: Parent Phone+PIN Login

Section 1: Endpoint Summary

Authenticates parent users using their registered phone number and PIN. Returns JWT token, parent profile, and all linked children for multi-child families.

Section 2: HTTP Details

  • HTTP Method: POST
  • Endpoint URL: /parent-app/parent-auth-verification
  • Content-Type: application/json
  • Authentication: Public (no token required)
  • Rate Limit: 5 requests per minute per IP

Section 3: Path Parameters

None required

Section 4: Query Parameters

None required

Section 5: Request Body Schema

{
  "phone_no*": "String (10-15 digits, with or without country code)",
  "pin*": "String (4-6 digit PIN)",
  "fcm_token": "String (Firebase Cloud Messaging device token)",
  "device_info": {
    "platform": "String (ios, android, web)",
    "model": "String (device model name)",
    "os_version": "String (operating system version)"
  }
}

Section 6: Response Schema (Success - 200)

{
  "status": "success",
  "message": "Authentication successful",
  "data": {
    "access_token": "JWT token string",
    "session_token": "UUID (unique per device)",
    "expires_in": "2592000 (seconds, 30 days)",
    "token_type": "Bearer",
    "parent": {
      "id": "UUID",
      "skole_id": "UUID",
      "father_phone": "String",
      "mother_phone": "String (nullable)",
      "email": "String (nullable)",
      "pin": "****",
      "father_name": "String",
      "mother_name": "String (nullable)",
      "fcm_token": "String (updated)"
    },
    "children": [
      {
        "id": "UUID",
        "skole_id": "UUID",
        "first_name": "String",
        "last_name": "String",
        "roll_no": "Integer",
        "class": "String",
        "section": "String",
        "status": "active"
      }
    ]
  }
}

Section 7: Error Responses

400 - Bad Request
{
  "status": "error",
  "code": "VALIDATION_ERROR",
  "message": "Validation failed",
  "errors": [
    {"field": "phone_no", "message": "phone_no is required"},
    {"field": "pin", "message": "PIN must be 4-6 digits"}
  ]
}
401 - Invalid Phone
{
  "status": "error",
  "code": "INVALID_PHONE",
  "message": "Phone number not registered",
  "timestamp": "2026-03-16T10:30:45.000Z"
}
401 - Invalid PIN
{
  "status": "error",
  "code": "INVALID_PIN",
  "message": "Incorrect PIN",
  "timestamp": "2026-03-16T10:30:45.000Z"
}
429 - Rate Limited
{
  "status": "error",
  "code": "RATE_LIMITED",
  "message": "Too many login attempts. Try again in 1 minute",
  "retry_after": 60
}

Section 8: Implementation Examples

JavaScript (Node.js/React Native):
const loginParent = async (phoneNo, pin, fcmToken) => {
  try {
    const response = await fetch('https://api.skole.com/v1/parent-app/parent-auth-verification', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        phone_no: phoneNo,
        pin: pin,
        fcm_token: fcmToken,
        device_info: {
          platform: 'android',
          model: 'samsung-galaxy-a12',
          os_version: '11.0'
        }
      })
    });

    const data = await response.json();
    
    if (data.status === 'success') {
      const { access_token, session_token, children } = data.data;
      
      // Store token securely (AsyncStorage for React Native)
      await AsyncStorage.setItem('authToken', access_token);
      await AsyncStorage.setItem('sessionToken', session_token);
      
      console.log(`Logged in. Found ${children.length} children`);
      return data.data;
    } else {
      console.error('Login failed:', data.message);
      throw new Error(data.message);
    }
  } catch (error) {
    console.error('Network error:', error);
    throw error;
  }
};

// Usage
await loginParent('9876543210', '1234', 'fcm_token_here');
Python:
import requests
import json
import asyncio

async def login_parent(phone_no: str, pin: str, fcm_token: str):
    url = 'https://api.skole.com/v1/parent-app/parent-auth-verification'
    
    payload = {
        'phone_no': phone_no,
        'pin': pin,
        'fcm_token': fcm_token,
        'device_info': {
            'platform': 'ios',
            'model': 'iphone-13',
            'os_version': '15.0'
        }
    }
    
    headers = {'Content-Type': 'application/json'}
    
    try:
        response = requests.post(url, json=payload, headers=headers, timeout=10)
        data = response.json()
        
        if data['status'] == 'success':
            token = data['data']['access_token']
            children = data['data']['children']
            print(f"Logged in successfully. Children: {len(children)}")
            return data['data']
        else:
            raise Exception(f"Login failed: {data['message']}")
    except requests.exceptions.RequestException as e:
        print(f"Network error: {e}")
        raise

# Usage
asyncio.run(login_parent('9876543210', '1234', 'fcm_token_here'))
cURL:
curl -X POST \
  'https://api.skole.com/v1/parent-app/parent-auth-verification' \
  -H 'Content-Type: application/json' \
  -d '{
    "phone_no": "9876543210",
    "pin": "1234",
    "fcm_token": "eZuB8bKWO3c:APA91bH...",
    "device_info": {
      "platform": "android",
      "model": "samsung-a12",
      "os_version": "11.0"
    }
  }'

Section 9: Database Context

Primary Table: parent_details
  • Primary Key: id (UUID)
  • School Isolation: skole_id
Query Flow:
  1. SELECT * FROM parent_details WHERE (father_phone = ? OR mother_phone = ?) AND skole_id = ?
  2. Verify: password.verify(pin, parent.pin_hash)
  3. SELECT * FROM students WHERE parent_id = ? AND status = ‘active’
  4. INSERT INTO parent_devices (parent_id, session_token, fcm_token, device_info, created_at)
Indexed Fields:
  • father_phone (for query performance)
  • mother_phone (for query performance)
  • skole_id (for school isolation)
  • session_token (for session lookup)
Data Retention: Keep parent_devices for 2 years, archive afterward

Section 10: Business Logic & Validations

Validation Rules:
  • phone_no: 10-15 digits, with or without country code, must exist in parent_details
  • pin: 4-6 digits, bcrypt hashed in database
  • skole_id: Must match user’s school
  • device_info: Optional but recommended
Authorization:
  • No prior authentication required (public endpoint)
  • After login, JWT validates on all subsequent requests
  • Parent can only access their own children and school data
Business Logic:
  1. Search for parent by father_phone or mother_phone
  2. If found, verify PIN against bcrypt hash
  3. If PIN matches, collect all active children linked to parent
  4. Generate unique session_token (UUID) with 30-day expiry
  5. Create parent_devices record for multi-device tracking
  6. Update FCM token for push notifications
  7. Sign JWT with parent type and return access_token
Special Behaviors:
  • PIN is never returned in response (masked as ****)
  • If parent has no PIN set, redirect to PIN setup flow
  • Multiple devices supported per parent (multi-login)
  • Old devices can be logged out via separate endpoint
  • PUT /parent-app/auth/device-info - Update device FCM token
  • POST /parent-app/logout - Logout and invalidate session
  • POST /web-app/parent-set-password - Setup PIN for first time
  • PATCH /web-app/parent-edit-password - Change existing PIN
  • GET /parent-app/student - Get children after login

Section 12: Response Summary Table

StatusScenarioError CodeMessage
200 ✅Success-Authentication successful
400 ❌Missing fieldsVALIDATION_ERRORValidation failed
401 ❌Phone not foundINVALID_PHONEPhone number not registered
401 ❌Wrong PININVALID_PINIncorrect PIN
429 ❌Rate limitRATE_LIMITEDToo many login attempts
500 ❌Server errorINTERNAL_ERRORServer error occurred

EP002: Parent Logout

Section 1: Endpoint Summary

Invalidates parent session by marking the device session as inactive, effectively logging out the parent from that specific device.

Section 2: HTTP Details

  • HTTP Method: POST
  • Endpoint URL: /parent-app/logout
  • Content-Type: application/json
  • Authentication: JWT Bearer Token (Required - Parent)
  • Rate Limit: Unlimited

Section 3: Path Parameters

None

Section 4: Query Parameters

ParameterTypeRequiredDefaultDescriptionExample
skole_idUUIDNoFrom JWTSchool identifier (optional override)550e8400-e29b-41d4-a716-446655440000

Section 5: Request Body Schema

{
  "logout_all_devices": "Boolean (optional, defaults to false)"
}

Section 6: Response Schema (Success - 200)

{
  "status": "success",
  "message": "Logged out successfully",
  "data": {
    "logged_out_devices": "Integer (1 if logout_all_devices=false, count if true)",
    "timestamp": "2026-03-16T10:30:45.000Z"
  }
}

Section 7: Error Responses

401 - Unauthorized
{
  "status": "error",
  "code": "UNAUTHORIZED",
  "message": "Authorization header missing or invalid"
}
404 - Session Not Found
{
  "status": "error",
  "code": "SESSION_NOT_FOUND",
  "message": "Active session not found"
}

Section 8: Implementation Examples

JavaScript:
const logoutParent = async (token, logoutAllDevices = false) => {
  try {
    const response = await fetch('https://api.skole.com/v1/parent-app/logout', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        logout_all_devices: logoutAllDevices
      })
    });

    const data = await response.json();
    
    if (data.status === 'success') {
      await AsyncStorage.removeItem('authToken');
      console.log('Logged out successfully');
      return true;
    } else {
      console.error('Logout failed:', data.message);
      return false;
    }
  } catch (error) {
    console.error('Network error:', error);
    throw error;
  }
};

// Usage
await logoutParent(token, false); // Logout from current device
await logoutParent(token, true);  // Logout from all devices
Python:
def logout_parent(token: str, logout_all_devices: bool = False) -> bool:
    url = 'https://api.skole.com/v1/parent-app/logout'
    headers = {
        'Authorization': f'Bearer {token}',
        'Content-Type': 'application/json'
    }
    data = {'logout_all_devices': logout_all_devices}
    
    try:
        response = requests.post(url, json=data, headers=headers, timeout=10)
        result = response.json()
        
        if result['status'] == 'success':
            print('Logout successful')
            return True
        else:
            print(f"Logout failed: {result['message']}")
            return False
    except requests.exceptions.RequestException as e:
        print(f"Network error: {e}")
        return False

# Usage
logout_parent(token, logout_all_devices=True)
cURL:
curl -X POST \
  'https://api.skole.com/v1/parent-app/logout' \
  -H 'Authorization: Bearer YOUR_TOKEN_HERE' \
  -H 'Content-Type: application/json' \
  -d '{"logout_all_devices": false}'

Section 9: Database Context

Tables Affected:
  • parent_devices - Mark is_active = 0
  • Query: UPDATE parent_devices SET is_active = 0 WHERE session_token = ? AND parent_id = ?
Behavior:
  • If logout_all_devices = false: Only invalidate current session_token
  • If logout_all_devices = true: Invalidate all session_tokens for the parent

Section 10: Business Logic & Validations

Validation:
  • JWT must be valid and not expired
  • Parent must have an active session
  • session_token must exist in parent_devices
Business Logic:
  1. Extract session_token from JWT
  2. Find parent_devices record where session_token matches
  3. Mark is_active = 0
  4. If logout_all_devices = true, also mark all other devices as inactive
  5. Return success message
  • POST /parent-app/parent-auth-verification - Login again
  • PUT /parent-app/auth/device-info - Manage device tokens
  • GET /parent-app/profile - Get parent profile after login

Section 12: Response Summary Table

StatusScenarioError CodeMessage
200 ✅Success-Logged out successfully
401 ❌Invalid tokenUNAUTHORIZEDAuthorization header missing or invalid
404 ❌Session not foundSESSION_NOT_FOUNDActive session not found
500 ❌Database errorINTERNAL_ERRORServer error occurred

EP003: Update Parent Device Info

Section 1: Endpoint Summary

Updates device information (FCM token, platform, model) for push notification delivery without requiring re-login.

Section 2: HTTP Details

  • HTTP Method: PUT
  • Endpoint URL: /parent-app/auth/device-info
  • Content-Type: application/json
  • Authentication: JWT Bearer Token (Required - Parent)
  • Rate Limit: 100 requests per minute

Section 3: Path Parameters

None

Section 4: Query Parameters

None

Section 5: Request Body Schema

{
  "fcm_token": "String (Firebase Cloud Messaging token)",
  "platform": "Enum: ios, android, web",
  "model": "String (device model name)",
  "os_version": "String (operating system version)"
}

Section 6: Response Schema (Success - 200)

{
  "status": "success",
  "message": "Device information updated",
  "data": {
    "device_id": "UUID",
    "fcm_token": "Updated token string",
    "platform": "ios",
    "model": "iphone-13-pro",
    "os_version": "15.2",
    "updated_at": "2026-03-16T10:30:45.000Z"
  }
}

Section 7: Error Responses

400 - Invalid FCM Token
{
  "status": "error",
  "code": "INVALID_FCM_TOKEN",
  "message": "FCM token format is invalid or expired"
}
401 - Unauthorized
{
  "status": "error",
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired JWT token"
}

Section 8: Implementation Examples

JavaScript:
const updateDeviceInfo = async (token, fcmToken) => {
  try {
    const response = await fetch('https://api.skole.com/v1/parent-app/auth/device-info', {
      method: 'PUT',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        fcm_token: fcmToken,
        platform: 'ios',
        model: 'iphone-13-pro',
        os_version: '15.2'
      })
    });

    const data = await response.json();
    if (data.status === 'success') {
      console.log('Device info updated');
      return true;
    } else {
      console.error('Update failed:', data.message);
      return false;
    }
  } catch (error) {
    console.error('Error:', error);
    return false;
  }
};
Python:
def update_device_info(token: str, fcm_token: str, platform: str) -> bool:
    url = 'https://api.skole.com/v1/parent-app/auth/device-info'
    headers = {
        'Authorization': f'Bearer {token}',
        'Content-Type': 'application/json'
    }
    data = {
        'fcm_token': fcm_token,
        'platform': platform,
        'model': 'samsung-galaxy-a12',
        'os_version': '11.0'
    }
    
    response = requests.put(url, json=data, headers=headers, timeout=10)
    result = response.json()
    return result['status'] == 'success'
cURL:
curl -X PUT \
  'https://api.skole.com/v1/parent-app/auth/device-info' \
  -H 'Authorization: Bearer YOUR_TOKEN_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
    "fcm_token": "eZuB8bKWO3c:APA91bH...",
    "platform": "android",
    "model": "samsung-a12",
    "os_version": "11.0"
  }'

Section 9: Database Context

Table: parent_devices Update Query: UPDATE parent_devices SET fcm_token = ?, platform = ?, model = ?, os_version = ?, updated_at = NOW() WHERE session_token = ?

Section 10: Business Logic & Validations

Validation:
  • FCM token must be valid format
  • Platform must be one of: ios, android, web
  • JWT must be valid
Business Logic:
  1. Extract session_token from JWT
  2. Find parent_devices record
  3. Update fcm_token, platform, model, os_version
  4. Return updated device information
  • POST /parent-app/logout - Logout device
  • POST /parent-app/parent-auth-verification - Initial login

Section 12: Response Summary Table

StatusScenarioError CodeMessage
200 ✅Success-Device information updated
400 ❌Invalid FCMINVALID_FCM_TOKENFCM token format invalid
401 ❌UnauthorizedUNAUTHORIZEDInvalid or expired JWT
500 ❌Server errorINTERNAL_ERRORServer error occurred

12. Summary Table: All 13 Authentication Endpoints

IDEndpointMethodAuthDescriptionStatus
EP001/parent-app/parent-auth-verificationPOSTPublicParent login✅ Documented
EP002/parent-app/logoutPOSTJWTParent logout✅ Documented
EP003/parent-app/auth/device-infoPUTJWTUpdate device info✅ Documented
EP004/teachers-app/teacher-auth-verificationPOSTPublicTeacher login⏳ See EP001 pattern
EP005/teachers-app/logoutPOSTJWTTeacher logout⏳ See EP002 pattern
EP006/web-app/signupPOSTPublicAdmin signup⏳ Coming
EP007/web-app/signinPOSTPublicAdmin login⏳ Coming
EP008/web-app/reset-passwordPOSTPublicPassword reset⏳ Coming
EP009/web-app/parent-set-passwordPOSTPublicSetup parent PIN⏳ Coming
EP010/web-app/parent-edit-passwordPATCHPublicChange parent PIN⏳ Coming
EP011/web-app/staff-set-passwordPOSTPublicSetup staff PIN⏳ Coming
EP012/web-app/staff-edit-passwordPATCHPublicChange staff PIN⏳ Coming
EP013/web-app/logoutPOSTJWTAdmin logout✅ Documented

EP004: Teacher Phone+PIN Login

Section 1: Endpoint Summary

Authenticates teacher/staff users using registered phone number and PIN. Returns JWT token and staff profile with assigned classes.

Section 2: HTTP Details

  • HTTP Method: POST
  • Endpoint URL: /teachers-app/teacher-auth-verification
  • Content-Type: application/json
  • Authentication: Public
  • Rate Limit: 5 requests per minute per IP

Section 5: Request Body Schema

{
  "phone_no*": "String (10-15 digits)",
  "pin*": "String (4-6 digit PIN)",
  "fcm_token": "String (Firebase token)"
}

Section 6: Response Schema (Success - 200)

{
  "status": "success",
  "message": "Authentication successful",
  "data": {
    "access_token": "JWT token",
    "session_token": "UUID",
    "expires_in": 2592000,
    "token_type": "Bearer",
    "staff": {
      "id": "UUID",
      "skole_id": "UUID",
      "phone_no": "9876543210",
      "email": "teacher@school.com",
      "first_name": "Vikram",
      "last_name": "Kumar",
      "designation": "Teacher",
      "department": "Science",
      "pin": "****"
    },
    "assigned_classes": [
      {
        "class_id": "UUID",
        "class_name": "10-A",
        "subject": "Biology"
      }
    ]
  }
}

Section 7: Error Responses

401 - Phone/PIN Invalid
{
  "status": "error",
  "code": "INVALID_CREDENTIALS",
  "message": "Phone number or PIN is incorrect"
}

Section 8: Implementation Examples

JavaScript:
const loginTeacher = async (phoneNo, pin, fcmToken) => {
  const response = await fetch('https://api.skole.com/v1/teachers-app/teacher-auth-verification', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      phone_no: phoneNo,
      pin: pin,
      fcm_token: fcmToken
    })
  });

  const data = await response.json();
  if (data.status === 'success') {
    await AsyncStorage.setItem('teacherToken', data.data.access_token);
    console.log(`Teacher ${data.data.staff.first_name} logged in`);
    return data.data;
  }
  throw new Error(data.message);
};

Section 9: Database Context

Tables:
  • staff_details - Teacher/admin staff records
  • staff_class_assignments - Assigned classes
Indices:
  • phone_no (for lookup)
  • skole_id (for school isolation)

Section 10: Business Logic & Validations

Validation:
  • phone_no must exist in staff_details
  • pin must match bcrypt hash
  • Staff must be active status
  • Must belong to correct school (skole_id)
Business Logic:
  1. Find staff by phone_no and skole_id
  2. Verify PIN
  3. Load all assigned classes
  4. Generate JWT with staff role
  5. Create staff_devices session record
  6. Return token and profile
  • POST /teachers-app/logout - Logout
  • GET /teachers-app/classes - Get assigned classes
  • GET /teachers-app/students - Get students by class

Section 12: Response Summary Table

StatusScenarioError CodeMessage
200 ✅Success-Authentication successful
401 ❌InvalidINVALID_CREDENTIALSPhone or PIN incorrect
429 ❌Rate limitedRATE_LIMITEDToo many attempts

EP005: Teacher Logout

Section 1: Endpoint Summary

Invalidates teacher session, effectively logging them out from the app.

Section 2: HTTP Details

  • HTTP Method: POST
  • Endpoint URL: /teachers-app/logout
  • Authentication: JWT Bearer Token (Staff)

Section 5: Request Body Schema

{
  "logout_all_devices": "Boolean (optional, default: false)"
}

Section 6: Response Schema (Success - 200)

{
  "status": "success",
  "message": "Logged out successfully",
  "data": {
    "logged_out_devices": 1,
    "timestamp": "2026-03-16T10:30:45.000Z"
  }
}

Section 7: Error Responses

401 - Unauthorized
{
  "status": "error",
  "code": "UNAUTHORIZED",
  "message": "Invalid JWT token"
}

Section 8: Implementation Examples

JavaScript:
const logoutTeacher = async (token) => {
  const response = await fetch('https://api.skole.com/v1/teachers-app/logout', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    }
  });

  const data = await response.json();
  if (data.status === 'success') {
    await AsyncStorage.removeItem('teacherToken');
    return true;
  }
  return false;
};

Section 9: Database Context

Tables:
  • staff_devices - Mark is_active = 0

Section 10: Business Logic & Validations

Mark session_token as inactive in staff_devices table.
  • POST /teachers-app/teacher-auth-verification - Login

Section 12: Response Summary Table

StatusScenarioError Code
200 ✅Success-
401 ❌UnauthorizedUNAUTHORIZED

EP006: Admin Signup

Section 1: Endpoint Summary

Creates new admin account with email and password. First admin in a school requires special code/invitation.

Section 2: HTTP Details

  • HTTP Method: POST
  • Endpoint URL: /web-app/signup
  • Authentication: Public (with optional invitation code)

Section 5: Request Body Schema

{
  "email*": "String (valid email format)",
  "password*": "String (min 8 chars, uppercase, number, special char)",
  "first_name*": "String",
  "last_name*": "String",
  "school_code": "String (optional, for first admin)",
  "invitation_code": "UUID (optional for additional admins)"
}

Section 6: Response Schema (Success - 201)

{
  "status": "success",
  "message": "Admin account created successfully",
  "data": {
    "id": "UUID",
    "email": "admin@school.com",
    "first_name": "Rajesh",
    "last_name": "Sharma",
    "created_at": "2026-03-16T10:30:45.000Z"
  }
}

Section 7: Error Responses

400 - Email Exists
{
  "status": "error",
  "code": "EMAIL_EXISTS",
  "message": "Email already registered"
}
400 - Weak Password
{
  "status": "error",
  "code": "WEAK_PASSWORD",
  "message": "Password must contain uppercase, number, and special character"
}
400 - Invalid Code
{
  "status": "error",
  "code": "INVALID_CODE",
  "message": "School code or invitation code is invalid"
}

Section 8: Implementation Examples

JavaScript:
const signupAdmin = async (email, password, firstName, lastName, schoolCode) => {
  const response = await fetch('https://api.skole.com/v1/web-app/signup', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email,
      password,
      first_name: firstName,
      last_name: lastName,
      school_code: schoolCode
    })
  });

  const data = await response.json();
  if (data.status === 'success') {
    console.log('Admin account created');
    return data.data;
  }
  throw new Error(data.message);
};

Section 9: Database Context

Tables:
  • users - Admin user records
  • schools - School codes validation
Password Hashing: bcrypt (12 rounds)

Section 10: Business Logic & Validations

Validation:
  • Email format valid and unique
  • Password strength check (8+ chars, uppercase, number, special char)
  • School code exists and valid
  • Invitation code not expired
Business Logic:
  1. Validate email uniqueness
  2. Check password strength
  3. If school_code provided, verify and create school + admin
  4. If invitation_code provided, verify it’s valid and not expired
  5. Hash password with bcrypt
  6. Create user and admin_role records
  7. Return confirmation
  • POST /web-app/signin - Login with created account
  • POST /web-app/reset-password - Password reset

Section 12: Response Summary Table

StatusScenarioError CodeMessage
201 ✅Success-Account created
400 ❌Email existsEMAIL_EXISTSAlready registered
400 ❌Weak passwordWEAK_PASSWORDPassword requirements
400 ❌Invalid codeINVALID_CODECode invalid

EP007: Admin Email+Password Login

Section 1: Endpoint Summary

Authenticates admin users using email and password. Returns JWT token with admin profile and school information.

Section 2: HTTP Details

  • HTTP Method: POST
  • Endpoint URL: /web-app/signin
  • Authentication: Public

Section 5: Request Body Schema

{
  "email*": "String (valid email)",
  "password*": "String",
  "remember_me": "Boolean (optional)"
}

Section 6: Response Schema (Success - 200)

{
  "status": "success",
  "message": "Login successful",
  "data": {
    "access_token": "JWT token",
    "refresh_token": "UUID (if remember_me=true)",
    "expires_in": 86400,
    "token_type": "Bearer",
    "user": {
      "id": "UUID",
      "email": "admin@school.com",
      "first_name": "Rajesh",
      "last_name": "Sharma",
      "role": "admin"
    },
    "school": {
      "id": "UUID",
      "name": "Greenfield Academy",
      "code": "GFA2024"
    }
  }
}

Section 7: Error Responses

401 - Invalid Credentials
{
  "status": "error",
  "code": "INVALID_CREDENTIALS",
  "message": "Email or password is incorrect"
}

Section 8: Implementation Examples

JavaScript:
const loginAdmin = async (email, password) => {
  const response = await fetch('https://api.skole.com/v1/web-app/signin', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email,
      password,
      remember_me: true
    })
  });

  const data = await response.json();
  if (data.status === 'success') {
    localStorage.setItem('adminToken', data.data.access_token);
    return data.data;
  }
  throw new Error(data.message);
};

Section 9: Database Context

Tables:
  • users - User records
  • admin_roles - Admin role assignments
  • schools - School details
Password Verification: bcrypt.compare()

Section 10: Business Logic & Validations

Validation:
  • Email exists in users table
  • Password matches bcrypt hash
  • User has admin role
  • Account not suspended
Business Logic:
  1. Find user by email
  2. Verify password
  3. Check if admin role exists
  4. Load school information
  5. Generate JWT (1 day expiry)
  6. If remember_me, generate refresh token (30 days)
  7. Return tokens and profile
  • POST /web-app/reset-password - Reset password
  • POST /web-app/logout - Logout
  • GET /web-app/profile - Get admin profile

Section 12: Response Summary Table

StatusScenarioError CodeMessage
200 ✅Success-Login successful
401 ❌InvalidINVALID_CREDENTIALSEmail/password incorrect
403 ❌Not adminFORBIDDENUser not admin

EP008: Request Password Reset

Section 1: Endpoint Summary

Sends password reset email link for admin accounts. Generates time-limited reset token (valid 1 hour).

Section 2: HTTP Details

  • HTTP Method: POST
  • Endpoint URL: /web-app/reset-password
  • Authentication: Public

Section 5: Request Body Schema

{
  "email*": "String (registered admin email)"
}

Section 6: Response Schema (Success - 200)

{
  "status": "success",
  "message": "Reset link sent to your email",
  "data": {
    "email": "admin@school.com",
    "reset_token_expires_in": "3600 seconds"
  }
}

Section 7: Error Responses

404 - Email Not Found
{
  "status": "error",
  "code": "EMAIL_NOT_FOUND",
  "message": "Email not registered"
}

Section 8: Implementation Examples

JavaScript:
const requestPasswordReset = async (email) => {
  const response = await fetch('https://api.skole.com/v1/web-app/reset-password', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email })
  });

  const data = await response.json();
  if (data.status === 'success') {
    console.log('Reset email sent');
    return true;
  }
  throw new Error(data.message);
};

Section 9: Database Context

Tables:
  • password_resets - Store reset token, email, expires_at
  • TTL: 1 hour, auto-cleanup for expired records

Section 10: Business Logic & Validations

Validation:
  • Email exists in users table
  • No active reset token for this email (or expired)
Business Logic:
  1. Find user by email
  2. Generate random reset token (32 chars)
  3. Save to password_resets with 1-hour expiry
  4. Send email with reset link
  5. Return confirmation (don’t expose token)
  • POST /web-app/confirm-reset - Verify token and set new password
  • POST /web-app/signin - Login

Section 12: Response Summary Table

StatusScenarioError CodeMessage
200 ✅Success-Reset link sent
404 ❌Not foundEMAIL_NOT_FOUNDEmail not registered

EP009: Set Parent PIN (First Time)

Section 1: Endpoint Summary

Parents set their initial 4-digit PIN. Requires parent invitation/verification code from admin. Used during initial parent account activation.

Section 2: HTTP Details

  • HTTP Method: POST
  • Endpoint URL: /web-app/parent-set-password
  • Authentication: Public (with activation code)

Section 5: Request Body Schema

{
  "parent_id*": "UUID",
  "activation_code*": "String (from SMS/email invitation)",
  "pin*": "String (4-6 digits only, no special chars)",
  "confirm_pin*": "String (must match pin)"
}

Section 6: Response Schema (Success - 200)

{
  "status": "success",
  "message": "PIN set successfully",
  "data": {
    "parent_id": "UUID",
    "pin_set_at": "2026-03-16T10:30:45.000Z",
    "message": "You can now login with your phone and PIN"
  }
}

Section 7: Error Responses

400 - Invalid PIN Format
{
  "status": "error",
  "code": "INVALID_PIN_FORMAT",
  "message": "PIN must be 4-6 digits only"
}
400 - PIN Mismatch
{
  "status": "error",
  "code": "PIN_MISMATCH",
  "message": "PIN and confirm PIN do not match"
}
400 - Invalid Activation Code
{
  "status": "error",
  "code": "INVALID_ACTIVATION_CODE",
  "message": "Activation code is invalid or expired"
}

Section 8: Implementation Examples

JavaScript:
const setParentPin = async (parentId, activationCode, pin) => {
  const response = await fetch('https://api.skole.com/v1/web-app/parent-set-password', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      parent_id: parentId,
      activation_code: activationCode,
      pin: pin,
      confirm_pin: pin
    })
  });

  const data = await response.json();
  if (data.status === 'success') {
    console.log('PIN set successfully');
    return true;
  }
  throw new Error(data.message);
};

Section 9: Database Context

Tables:
  • parent_details - Update pin_hash
  • parent_activations - Track activation codes (TTL: 7 days)

Section 10: Business Logic & Validations

Validation:
  • PIN format: 4-6 digits only
  • PIN matches confirm_pin
  • activation_code exists and not expired
  • parent_id matches activation code
  • Parent doesn’t already have a PIN set
Business Logic:
  1. Validate PIN format
  2. Verify activation_code for this parent_id
  3. Hash PIN with bcrypt
  4. Update parent_details with pin_hash
  5. Mark activation as completed
  6. Return success
  • POST /parent-app/parent-auth-verification - Login after PIN set
  • PATCH /web-app/parent-edit-password - Change PIN later

Section 12: Response Summary Table

StatusScenarioError CodeMessage
200 ✅Success-PIN set successfully
400 ❌Invalid formatINVALID_PIN_FORMATMust be 4-6 digits
400 ❌PIN mismatchPIN_MISMATCHPins don’t match
400 ❌Code expiredINVALID_ACTIVATION_CODECode invalid

EP010: Update Parent PIN

Section 1: Endpoint Summary

Parents change their existing PIN. Requires old PIN verification for security.

Section 2: HTTP Details

  • HTTP Method: PATCH
  • Endpoint URL: /web-app/parent-edit-password
  • Authentication: Public (with parent identification)

Section 5: Request Body Schema

{
  "phone_no*": "String",
  "old_pin*": "String",
  "new_pin*": "String (4-6 digits)",
  "confirm_pin*": "String"
}

Section 6: Response Schema (Success - 200)

{
  "status": "success",
  "message": "PIN updated successfully",
  "data": {
    "updated_at": "2026-03-16T10:30:45.000Z"
  }
}

Section 7: Error Responses

401 - Old PIN Incorrect
{
  "status": "error",
  "code": "INVALID_OLD_PIN",
  "message": "Old PIN is incorrect"
}

Section 8: Implementation Examples

JavaScript:
const updateParentPin = async (phone, oldPin, newPin) => {
  const response = await fetch('https://api.skole.com/v1/web-app/parent-edit-password', {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      phone_no: phone,
      old_pin: oldPin,
      new_pin: newPin,
      confirm_pin: newPin
    })
  });

  const data = await response.json();
  return data.status === 'success';
};

Section 9: Database Context

Tables:
  • parent_details - Update pin_hash, updated_at

Section 10: Business Logic & Validations

Verify old PIN, validate new PIN format, update in database.
  • POST /parent-app/parent-auth-verification - Login with new PIN
  • POST /web-app/parent-set-password - Initial setup

Section 12: Response Summary Table

StatusScenarioError CodeMessage
200 ✅Success-PIN updated
401 ❌Wrong old PININVALID_OLD_PINIncorrect old PIN

EP011: Set Teacher PIN (First Time)

Section 1: Endpoint Summary

Teachers set their initial 4-digit PIN during account activation.

Section 2: HTTP Details

  • HTTP Method: POST
  • Endpoint URL: /web-app/staff-set-password
  • Authentication: Public (with activation code)

Section 5: Request Body Schema

{
  "staff_id*": "UUID",
  "activation_code*": "String",
  "pin*": "String (4-6 digits)",
  "confirm_pin*": "String"
}

Section 6: Response Schema (Success - 200)

{
  "status": "success",
  "message": "PIN set successfully"
}

Section 8: Implementation Examples

JavaScript:
const setTeacherPin = async (staffId, code, pin) => {
  const response = await fetch('https://api.skole.com/v1/web-app/staff-set-password', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      staff_id: staffId,
      activation_code: code,
      pin: pin,
      confirm_pin: pin
    })
  });

  return (await response.json()).status === 'success';
};

Section 9: Database Context

Tables:
  • staff_details - Update pin_hash
  • staff_activations - Verify activation code

Section 12: Response Summary Table

StatusScenarioError Code
200 ✅Success-
400 ❌InvalidFORMAT_ERROR

EP012: Update Teacher PIN

Section 1: Endpoint Summary

Teachers change their existing PIN with old PIN verification.

Section 2: HTTP Details

  • HTTP Method: PATCH
  • Endpoint URL: /web-app/staff-edit-password
  • Authentication: Public

Section 5: Request Body Schema

{
  "phone_no*": "String",
  "old_pin*": "String",
  "new_pin*": "String",
  "confirm_pin*": "String"
}

Section 6: Response Schema (Success - 200)

{
  "status": "success",
  "message": "PIN updated successfully"
}

Section 8: Implementation Examples

JavaScript:
const updateTeacherPin = async (phone, oldPin, newPin) => {
  const response = await fetch('https://api.skole.com/v1/web-app/staff-edit-password', {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      phone_no: phone,
      old_pin: oldPin,
      new_pin: newPin,
      confirm_pin: newPin
    })
  });

  return (await response.json()).status === 'success';
};

Section 9: Database Context

Tables:
  • staff_details - Update pin_hash

Section 12: Response Summary Table

StatusScenarioError Code
200 ✅Success-
401 ❌Wrong PININVALID_OLD_PIN

EP013: Admin Logout

Section 1: Endpoint Summary

Invalidates admin JWT token and ends the session.

Section 2: HTTP Details

  • HTTP Method: POST
  • Endpoint URL: /web-app/logout
  • Authentication: JWT Bearer Token (Required)

Section 5: Request Body Schema

{} // Empty body

Section 6: Response Schema (Success - 200)

{
  "status": "success",
  "message": "Logged out successfully"
}

Section 7: Error Responses

401 - Unauthorized
{
  "status": "error",
  "code": "UNAUTHORIZED",
  "message": "Invalid JWT token"
}

Section 8: Implementation Examples

JavaScript:
const logoutAdmin = async (token) => {
  const response = await fetch('https://api.skole.com/v1/web-app/logout', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    }
  });

  if (response.ok) {
    localStorage.removeItem('adminToken');
    return true;
  }
  return false;
};

Section 9: Database Context

Tables:
  • sessions - Revoke session token or mark as inactive

Section 10: Business Logic & Validations

Extract and invalidate JWT session.
  • POST /web-app/signin - Login again

Section 12: Response Summary Table

StatusScenarioError Code
200 ✅Success-
401 ❌UnauthorizedUNAUTHORIZED