Admin Role Assignment Guide

This guide explains how to assign and revoke admin privileges in Archery Apprentice. Admin roles are managed through Firebase Authentication custom claims.

Overview

Admin status is stored as a Firebase Auth custom claim:

{
  "admin": true
}

When a user has this claim, the app grants them access to the Admin Panel and all moderation capabilities.


Assignment Methods

Best for: One-off assignments, small admin team

  1. Open Firebase Console
  2. Select your project
  3. Navigate to AuthenticationUsers
  4. Find the user by email or UID
  5. Click the menu → Edit user
  6. Scroll to Custom claims
  7. Enter: {"admin": true}
  8. Click Save
┌─────────────────────────────────────────────────────────────────┐
│                    Firebase Console                              │
│                                                                  │
│  Authentication > Users                                          │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ Email              │ UID              │ Actions             │ │
│  ├────────────────────┼──────────────────┼─────────────────────┤ │
│  │ admin@example.com  │ abc123...        │ ⋮ → Edit user       │ │
│  └────────────────────┴──────────────────┴─────────────────────┘ │
│                                                                  │
│  Edit User                                                       │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ Custom claims:                                              │ │
│  │ ┌────────────────────────────────────────────────────────┐ │ │
│  │ │ {"admin": true}                                        │ │ │
│  │ └────────────────────────────────────────────────────────┘ │ │
│  │                                         [Save]             │ │
│  └────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

Method 2: Firebase Admin SDK (Programmatic)

Best for: Automated assignment, CI/CD workflows

// Node.js with Firebase Admin SDK
const admin = require('firebase-admin');
 
admin.initializeApp();
 
async function setAdminClaim(uid) {
  await admin.auth().setCustomUserClaims(uid, { admin: true });
  console.log(`Admin claim set for user ${uid}`);
}
 
// Usage
setAdminClaim('firebase-uid-of-user');

Method 3: Cloud Function (Self-Service with Approval)

Best for: Larger organizations with approval workflows

// Cloud Function for admin role requests
exports.requestAdminRole = functions.https.onCall(async (data, context) => {
  // Log request for manual approval
  await admin.firestore().collection('admin_requests').add({
    requesterId: context.auth.uid,
    requestedAt: admin.firestore.FieldValue.serverTimestamp(),
    reason: data.reason,
    status: 'pending'
  });
 
  return { message: 'Request submitted for approval' };
});
 
// Separate function for approval (manual trigger)
exports.approveAdminRequest = functions.https.onCall(async (data, context) => {
  // Verify caller is existing admin
  if (!context.auth.token.admin) {
    throw new functions.https.HttpsError('permission-denied', 'Not an admin');
  }
 
  await admin.auth().setCustomUserClaims(data.targetUid, { admin: true });
 
  // Update request status
  await admin.firestore()
    .collection('admin_requests')
    .doc(data.requestId)
    .update({ status: 'approved', approvedBy: context.auth.uid });
 
  return { message: 'Admin role granted' };
});

Revocation

Via Firebase Console

  1. Navigate to AuthenticationUsers
  2. Find the user
  3. Click Edit user
  4. Change custom claims to: {"admin": false} or clear the field
  5. Click Save

Via Admin SDK

async function revokeAdminClaim(uid) {
  await admin.auth().setCustomUserClaims(uid, { admin: false });
  console.log(`Admin claim revoked for user ${uid}`);
}

Claim Propagation

Token Refresh Required

Custom claims are embedded in the ID token. After changing claims:

  1. User must sign out and sign in again, OR
  2. App must force token refresh: user.getIdToken(forceRefresh: true)

App-Side Refresh

Android:

val tokenResult = auth.currentUser?.getIdToken(true)?.await()
val isAdmin = tokenResult?.claims?.get("admin") == true

iOS:

let tokenResult = try await user.getIDTokenResult(forcingRefresh: true)
let isAdmin = tokenResult.claims["admin"] as? Bool ?? false

Verification

Check Current Claims (Console)

  1. Go to AuthenticationUsers
  2. Find user and click Edit user
  3. View Custom claims field

Check via Admin SDK

async function checkAdminStatus(uid) {
  const user = await admin.auth().getUser(uid);
  const isAdmin = user.customClaims?.admin === true;
  console.log(`User ${uid} is admin: ${isAdmin}`);
  return isAdmin;
}

Check via REST API

curl -X POST \
  "https://identitytoolkit.googleapis.com/v1/accounts:lookup?key=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"idToken": "USER_ID_TOKEN"}'

Security Considerations

Never Trust Client-Side Only

Always verify admin status server-side (Firestore security rules):

function isAdmin() {
  return request.auth != null
         && request.auth.token.admin == true;
}
 
match /admin_audit_logs/{logId} {
  allow read, create: if isAdmin();
}

Limit Admin Count

Keep the admin team small to:

  • Reduce attack surface
  • Simplify access control
  • Maintain accountability

Audit Admin Changes

Log all admin role changes:

exports.onAdminClaimChange = functions.auth.user().beforeCreate(async (user) => {
  // This doesn't catch claim updates, but logs new users
});
 
// Better: Create an admin when setting claim
async function setAdminClaimWithAudit(uid, grantedBy) {
  await admin.auth().setCustomUserClaims(uid, { admin: true });
 
  await admin.firestore().collection('admin_role_changes').add({
    targetUid: uid,
    grantedBy: grantedBy,
    action: 'grant',
    timestamp: admin.firestore.FieldValue.serverTimestamp()
  });
}

Troubleshooting

”Access Denied” After Setting Claim

Cause: Token not refreshed after claim change

Solution:

  1. User signs out and signs back in, OR
  2. Force token refresh in app:
    auth.currentUser?.getIdToken(true)

Claim Not Showing in Console

Cause: Console may cache user data

Solution:

  1. Refresh the Firebase Console page
  2. Wait a few seconds and check again

Admin Status Lost After Sign Out

Cause: Claims are stored in Firebase Auth, not locally

Solution: This is expected behavior. Claims persist in Firebase and are reloaded on sign in.


Best Practices

PracticeReason
Use Firebase Console for small teamsSimple, no code required
Document all admin assignmentsAccountability
Review admin list periodicallyRemove stale accounts
Use strong passwords + 2FAProtect admin accounts
Test in development firstVerify claim propagation