Menu

Firebase Authentication Connector - Usage Guide

The firebase connector provides Firebase Authentication support for Aussom Server applications. It wraps the Google Firebase Admin Java SDK and enables server-side token verification, user management, and email action link generation.

Prerequisites

Before using this connector you need:

  1. A Firebase project — create one at console.firebase.google.com
  2. A service account JSON key — generated from Firebase Console under Project Settings > Service Accounts > "Generate new private key"
  3. Email/Password sign-in enabled — under Authentication > Sign-in method in the Firebase Console (if using email/password auth)

Place the service account JSON file in your app's app_data/ directory.

Getting Started

Include the firebase module and create an instance with your service account credentials:

include firebase;
include file;

class myapp : AppBase {
    private fb = null;

    private initFb() {
        if (this.fb == null) {
            this.fb = new firebase(file.read("app_data/service-account.json"));
        }
    }
}

The firebase constructor takes the service account JSON as a string. Using file.read() to load it from app_data/ is the recommended approach. Use lazy initialization (as shown above) rather than initializing in the class constructor.


Token Verification (OAuth2 Flow)

This is the primary use case. Your client app (web or mobile) authenticates users via the Firebase Client SDK and sends the resulting ID token to your Aussom Server for verification.

How the flow works:

  1. Client signs in via Firebase (Google, Facebook, email/password, phone, etc.)
  2. Client sends the ID token to your server (typically in a header)
  3. Server verifies the token and gets the user's identity

Verify an ID Token

public protectedEndpoint(req) {
    this.initFb();

    // Get the token from the Authorization header
    authHeader = req.getReq().headers["authorization"];
    if (!authHeader) {
        req.setStatusCode(401);
        req.send({ error: "Missing Authorization header" }.toJson());
        return;
    }

    // Strip "Bearer " prefix
    idToken = authHeader.replace("Bearer ", "");

    try {
        // Verify the token
        token = this.fb.verifyIdToken(idToken);

        // token is a map with these keys:
        //   uid           - string, the user's unique ID
        //   email         - string or null
        //   name          - string or null (display name)
        //   emailVerified - bool
        //   issuer        - string
        //   picture       - string or null (photo URL)
        //   claims        - map with all token claims

        req.putHeader("content-type", "application/json");
        req.send({ message: "Hello " + token.name, uid: token.uid }.toJson());
    } catch(e) {
        req.setStatusCode(401);
        req.send({ error: "Invalid token", details: "" + e }.toJson());
    }
}

Verify with Revocation Check

Pass true as the second argument to also check if the token has been revoked:

token = this.fb.verifyIdToken(idToken, true);

This makes an additional check against Firebase to ensure the user hasn't had their tokens revoked (e.g., after a password change or account disable).


User Management

Get a User

Retrieve user information by UID, email, or phone number:

// By UID
user = this.fb.getUser("abc123");

// By email
user = this.fb.getUserByEmail("user@example.com");

// By phone number (E.164 format)
user = this.fb.getUserByPhone("+15555551234");

All three return a user map with this structure:

{
    uid: "abc123",
    email: "user@example.com",
    displayName: "John Doe",
    phoneNumber: "+15555551234",
    photoUrl: "https://example.com/photo.jpg",
    emailVerified: true,
    disabled: false,
    providers: [
        { providerId: "google.com", uid: "...", email: "...", displayName: "..." },
        { providerId: "password", uid: "...", email: "...", displayName: null }
    ],
    customClaims: { role: "admin" },
    creationTime: 1700000000000,
    lastSignInTime: 1700100000000
}

The providers list shows which sign-in methods the user has linked (e.g., google.com, facebook.com, password for email/password).

Create a User

user = this.fb.createUser({
    email: "newuser@example.com",
    password: "securePassword123",
    displayName: "New User",
    emailVerified: false,
    disabled: false
});

// user.uid now contains the new user's UID

All fields are optional. Supported keys:

Key Type Description
email string Email address
password string Password (min 6 characters)
displayName string Display name
phoneNumber string Phone in E.164 format
photoUrl string Photo URL
emailVerified bool Whether email is verified
disabled bool Whether account is disabled

Update a User

user = this.fb.updateUser("abc123", {
    displayName: "Updated Name",
    emailVerified: true
});

Takes the user's UID as the first argument and an options map with the fields to update. Same supported keys as createUser.

Delete a User

this.fb.deleteUser("abc123");

Returns null on success. Throws if the user doesn't exist.

List Users

// First page (up to 1000 users)
result = this.fb.listUsers();

// Custom page size
result = this.fb.listUsers("", 50);

// Next page
result = this.fb.listUsers(result.nextPageToken, 50);

Returns a map:

{
    users: [ ... ],          // list of user maps
    nextPageToken: "..."     // empty string if no more pages
}

Custom Claims (Role-Based Access Control)

Custom claims are key-value pairs attached to a user that are included in their ID tokens. Use them for role-based access control.

Set Claims

this.fb.setCustomClaims("abc123", {
    role: "admin",
    permissions: ["read", "write", "delete"],
    level: 5
});

Claims are included in the user's next ID token. Existing tokens won't reflect the change until they're refreshed.

Read Claims

After setting claims, they appear in the user record and in verified tokens:

// From a user record
user = this.fb.getUser("abc123");
role = user.customClaims.role;    // "admin"

// From a verified token
token = this.fb.verifyIdToken(idToken);
role = token.claims.role;         // "admin"

Clear Claims

Pass an empty map to remove all custom claims:

this.fb.setCustomClaims("abc123", {});

Example: Role-Based Middleware

public adminOnly(req) {
    this.initFb();

    authHeader = req.getReq().headers["authorization"];
    if (!authHeader) {
        req.setStatusCode(401);
        req.send({ error: "Unauthorized" }.toJson());
        return;
    }

    try {
        token = this.fb.verifyIdToken(authHeader.replace("Bearer ", ""));

        // Check for admin role in claims
        if (token.claims.role != "admin") {
            req.setStatusCode(403);
            req.send({ error: "Forbidden: admin role required" }.toJson());
            return;
        }

        // User is admin, proceed
        req.putHeader("content-type", "application/json");
        req.send({ message: "Admin access granted", uid: token.uid }.toJson());
    } catch(e) {
        req.setStatusCode(401);
        req.send({ error: "" + e }.toJson());
    }
}

Session Cookies

Session cookies are an alternative to ID tokens for web applications. They're longer-lived and can be verified server-side without hitting Firebase on every request.

Create a Session Cookie

// Create a cookie that expires in 5 days (in milliseconds)
fiveDaysMs = 5 * 24 * 60 * 60 * 1000;
cookie = this.fb.createSessionCookie(idToken, fiveDaysMs);

// Set it on the response
req.setCookie("session", cookie, fiveDaysMs / 1000, true, "/");

The expiration must be between 5 minutes and 14 days (in milliseconds).

Verify a Session Cookie

public sessionProtected(req) {
    this.initFb();

    cookies = req.getReqCookies();
    sessionCookie = "";
    for (cookie : cookies) {
        if (cookie.name == "session") {
            sessionCookie = cookie.value;
        }
    }

    if (sessionCookie == "") {
        req.setStatusCode(401);
        req.send({ error: "No session" }.toJson());
        return;
    }

    try {
        token = this.fb.verifySessionCookie(sessionCookie, true);
        req.putHeader("content-type", "application/json");
        req.send({ uid: token.uid, email: token.email }.toJson());
    } catch(e) {
        req.setStatusCode(401);
        req.send({ error: "Invalid session" }.toJson());
    }
}

Custom Tokens

Custom tokens allow your server to create Firebase tokens for users who authenticate through an external system. The client SDK can then use signInWithCustomToken() to sign in.

// Simple custom token
token = this.fb.createCustomToken("user-uid-123");

// With custom claims embedded in the token
token = this.fb.createCustomToken("user-uid-123", {
    premiumAccount: true,
    tier: "gold"
});

The client uses this token to sign in:

// Client-side JavaScript
firebase.auth().signInWithCustomToken(customToken);

Email Action Links

Generate links for password reset, email verification, and email sign-in. These links can be sent via your own email system (e.g., SendGrid) instead of using Firebase's built-in email templates.

Password Reset Link

link = this.fb.generatePasswordResetLink("user@example.com");
// Send this link to the user via email

Email Verification Link

link = this.fb.generateEmailVerificationLink("user@example.com");
// Send this link to the user via email

Sign-In with Email Link

link = this.fb.generateSignInWithEmailLink("user@example.com", {
    url: "https://myapp.com/complete-signin",
    handleCodeInApp: true
});

Example: Custom Password Reset with SendGrid

include firebase;
include sendgrid;
include file;

class myapp : AppBase {
    private fb = null;
    private sgKey = "encrypted-sendgrid-key-here";

    private initFb() {
        if (this.fb == null) {
            this.fb = new firebase(file.read("app_data/service-account.json"));
        }
    }

    public resetPassword(req) {
        this.initFb();

        body = json.parse(req.getBody());
        email = body.email;

        try {
            link = this.fb.generatePasswordResetLink(email);

            // Send via SendGrid
            sg = new sendgrid(props.decrypt(this.sgKey));
            sg
                .from("noreply@myapp.com")
                .to(email)
                .subject("Reset Your Password")
                .content("Click here to reset your password: " + link)
                .send()
            ;

            req.putHeader("content-type", "application/json");
            req.send({ status: "Reset email sent" }.toJson());
        } catch(e) {
            req.setStatusCode(500);
            req.send({ error: "" + e }.toJson());
        }
    }
}

Token Revocation

Force a user to re-authenticate on all devices by revoking their refresh tokens:

this.fb.revokeRefreshTokens("abc123");

After revoking, use verifyIdToken(token, true) with the revocation check enabled to reject tokens issued before the revocation.


Error Handling

All firebase methods throw exceptions on failure. Catch them with try/catch and convert to string with "" + e to get the error message:

try {
    user = this.fb.getUser("nonexistent-uid");
} catch(e) {
    estr = "" + e;
    // estr contains: "firebase.getUser(): USER_NOT_FOUND: ..."
}

Error messages follow the format: firebase.<method>(): <ERROR_CODE>: <message>

Common Error Codes

Error Code Meaning
USER_NOT_FOUND No user with the given identifier
EMAIL_ALREADY_EXISTS Email is already in use
INVALID_ID_TOKEN Token is malformed
EXPIRED_ID_TOKEN Token has expired
REVOKED_ID_TOKEN Token was revoked
INVALID_SESSION_COOKIE Session cookie is invalid
EXPIRED_SESSION_COOKIE Session cookie has expired
REVOKED_SESSION_COOKIE Session cookie was revoked

Example: Specific Error Handling

try {
    user = this.fb.createUser({ email: "taken@example.com", password: "pass123" });
} catch(e) {
    estr = "" + e;
    if (estr.contains("EMAIL_ALREADY_EXISTS")) {
        // Handle duplicate email
        req.setStatusCode(409);
        req.send({ error: "Email already in use" }.toJson());
    } else {
        req.setStatusCode(500);
        req.send({ error: estr }.toJson());
    }
}

Complete Working Example

A small but complete app showing the common authentication patterns:

include firebase;
include file;

@API(
    version = "1.0.0",
    contactName = "My App",
    contactUrl = "https://myapp.com"
)
class myapp : AppBase {
    private fb = null;

    private initFb() {
        if (this.fb == null) {
            this.fb = new firebase(file.read("app_data/service-account.json"));
        }
    }

    /**
     * GET / - Public endpoint, no auth required.
     */
    public index(req) {
        req.putHeader("content-type", "application/json");
        req.send({ status: "ok", message: "Welcome" }.toJson());
    }

    /**
     * POST /signup - Create a new user account.
     * Body: { "email": "...", "password": "...", "name": "..." }
     */
    public signup(req) {
        this.initFb();
        try {
            body = json.parse(req.getBody());
            user = this.fb.createUser({
                email: body.email,
                password: body.password,
                displayName: body.name
            });

            // Set default role
            this.fb.setCustomClaims(user.uid, { role: "user" });

            req.putHeader("content-type", "application/json");
            req.send({
                uid: user.uid,
                email: user.email,
                displayName: user.displayName
            }.toJson());
        } catch(e) {
            estr = "" + e;
            if (estr.contains("EMAIL_ALREADY_EXISTS")) {
                req.setStatusCode(409);
                req.send({ error: "Email already registered" }.toJson());
            } else {
                req.setStatusCode(400);
                req.send({ error: estr }.toJson());
            }
        }
    }

    /**
     * GET /profile - Get the authenticated user's profile.
     * Requires: Authorization: Bearer <idToken>
     */
    public profile(req) {
        this.initFb();

        authHeader = req.getReq().headers["authorization"];
        if (!authHeader) {
            req.setStatusCode(401);
            req.send({ error: "Missing Authorization header" }.toJson());
            return;
        }

        try {
            token = this.fb.verifyIdToken(authHeader.replace("Bearer ", ""));
            user = this.fb.getUser(token.uid);

            req.putHeader("content-type", "application/json");
            req.send({
                uid: user.uid,
                email: user.email,
                displayName: user.displayName,
                emailVerified: user.emailVerified,
                role: user.customClaims.role,
                providers: user.providers
            }.toJson());
        } catch(e) {
            req.setStatusCode(401);
            req.send({ error: "Authentication failed" }.toJson());
        }
    }

    /**
     * POST /promote - Promote a user to admin (admin only).
     * Requires: Authorization: Bearer <idToken> (must be admin)
     * Body: { "uid": "..." }
     */
    public promote(req) {
        this.initFb();

        authHeader = req.getReq().headers["authorization"];
        if (!authHeader) {
            req.setStatusCode(401);
            req.send({ error: "Unauthorized" }.toJson());
            return;
        }

        try {
            // Verify the caller is an admin
            callerToken = this.fb.verifyIdToken(authHeader.replace("Bearer ", ""));
            if (callerToken.claims.role != "admin") {
                req.setStatusCode(403);
                req.send({ error: "Admin access required" }.toJson());
                return;
            }

            // Promote the target user
            body = json.parse(req.getBody());
            this.fb.setCustomClaims(body.uid, { role: "admin" });

            req.putHeader("content-type", "application/json");
            req.send({ status: "User promoted to admin" }.toJson());
        } catch(e) {
            req.setStatusCode(500);
            req.send({ error: "" + e }.toJson());
        }
    }

    /**
     * GET /users - List all users (admin only).
     * Requires: Authorization: Bearer <idToken> (must be admin)
     */
    public users(req) {
        this.initFb();

        authHeader = req.getReq().headers["authorization"];
        if (!authHeader) {
            req.setStatusCode(401);
            req.send({ error: "Unauthorized" }.toJson());
            return;
        }

        try {
            callerToken = this.fb.verifyIdToken(authHeader.replace("Bearer ", ""));
            if (callerToken.claims.role != "admin") {
                req.setStatusCode(403);
                req.send({ error: "Admin access required" }.toJson());
                return;
            }

            result = this.fb.listUsers("", 100);
            req.putHeader("content-type", "application/json");
            req.send(result.toJson());
        } catch(e) {
            req.setStatusCode(500);
            req.send({ error: "" + e }.toJson());
        }
    }
}

API Reference

Constructor

Method Parameters Returns
firebase(ServiceAccountJson) string - service account JSON this object

Token Operations

Method Parameters Returns
verifyIdToken(IdToken, CheckRevoked) string, bool (default: false) map - decoded token
createSessionCookie(IdToken, ExpiresInMs) string, int string - session cookie
verifySessionCookie(Cookie, CheckRevoked) string, bool (default: false) map - decoded token
createCustomToken(Uid, Claims) string, map (default: {}) string - custom token

User Management

Method Parameters Returns
getUser(Uid) string map - user record
getUserByEmail(Email) string map - user record
getUserByPhone(PhoneNumber) string (E.164 format) map - user record
createUser(Options) map map - user record
updateUser(Uid, Options) string, map map - user record
deleteUser(Uid) string null
setCustomClaims(Uid, Claims) string, map null
revokeRefreshTokens(Uid) string null
listUsers(PageToken, MaxResults) string (default: ""), int (default: 1000) map - {users, nextPageToken}

Email Action Links

Method Parameters Returns
generatePasswordResetLink(Email) string string - link
generateEmailVerificationLink(Email) string string - link
generateSignInWithEmailLink(Email, Settings) string, map string - link