Skip to main content

Firebase Authentication in Chrome Extensions: Solving the Blue Argon Error

·822 words·4 mins
Sebastian Scheibe
Author
Sebastian Scheibe
Table of Contents

Problem
#

Hey there, have you seen the Blue Argon error while publishing a Chrome extension with the Firebase Auth SDK?

Let’s get into the details of what a Blue Argon error is.

Blue Argon
#

Blue Argon is a rejection error enforced by the Chrome Web Store when a Chrome extension violates the security policies of Manifest V3.

Manifest V3 introduces stricter Content Security Policy (CSP) rules to enhance security, primarily targeting:

  1. Remote Code Execution (RCE): Prevents dynamically loading or injecting JavaScript code.
  2. External Script Loading: Prohibits fetching and executing scripts hosted remotely.

Extensions that use patterns like document.createElement('script') to dynamically load scripts—common in libraries like the Firebase Auth SDK—are automatically flagged and rejected with the Blue Argon error.

Example Problem Code
#

I encountered the Blue Argon rejection error while trying to use the following code, which dynamically appends a script to the DOM:

function loadJS(e) {
    return new Promise((t, n) => {
        const r = document.createElement("script");
        r.setAttribute("src", e);
        r.onload = t;
        r.onerror = i => {
            const s = tt("internal-error");
            s.customData = i, n(s)
        };
        r.type = "text/javascript";
        r.charset = "UTF-8";
        c$().appendChild(r);
    })
}

Manifest V3 forbids remote code to be included. See here for more info on Manifest V3 .

Solution
#

So, what if you want to use Firebase Auth inside your Chrome extension? Well, have you thought about using the REST API instead of the Firebase Auth SDK?

I have prepared a class that can cover most of the scenarios for my Firebase Auth needs.

export class HttpError extends Error {
    statusCode: number;
    data: any;

    constructor(message: string, statusCode: number, data = null) {
        super(message);
        this.name = 'HttpError';
        this.statusCode = statusCode;
        this.data = data;
    }
}


export interface FirebaseUser {
  kind: string;
  localId: string;
  email: string;
  displayName: string;
  idToken: string;
  registered: boolean;
  refreshToken: string;
  expiresIn: string;
}

export interface FirebaseSignupResponse {
  kind: string;
  idToken: string;
  email: string;
  refreshToken: string;
  expiresIn: string;
  localId: string;
}

export interface FirebaseRefreshTokenResponse {
  access_token: string;
  expires_in: string;
  token_type: string;
  refresh_token: string;
  id_token: string;
  user_id: string;
  project_id: string;
}

export interface FirebaseAuthURIResponse {
  kind: string;
  allProviders?: Array<'password'>;
  registered: boolean;
  sessionId: string;
  signinMethods?: Array<'password'>;
}

export interface FirebaseUpdateProfileResponse {
  kind: string;
  localId: string;
  email: string;
  displayName: string;
  providerUserInfo: ProviderUserInfo[];
  passwordHash: string;
  emailVerified: boolean;
}

export interface ProviderUserInfo {
  providerId: string;
  displayName: string;
  federatedId: string;
  email: string;
  rawId: string;
}

//https://firebase.google.com/docs/reference/rest/auth

export class FirebaseAuthCustom {
  apiKey: string;
  tokenUrl = 'https://securetoken.googleapis.com/v1/token';
  baseUrl = 'https://identitytoolkit.googleapis.com/v1/accounts';

  constructor(apiKey: string) {
    if (!apiKey) {
      throw new Error('API key is required to use FirebaseAuth');
    }
    this.apiKey = apiKey;
  }

  async request(endpoint: string, body: any) {
    const url = `${this.baseUrl}:${endpoint}?key=${this.apiKey}`;
    const response = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    });
    const data = await response.json();
    if (!response.ok) {
      throw new Error(data.error?.message || 'Request failed');
    }
    return data;
  }

  async signUp(email: string, password: string): Promise<FirebaseSignupResponse> {
    return this.request('signUp', {
      email,
      password,
      returnSecureToken: true,
    });
  }

  async signIn(email: string, password: string): Promise<FirebaseUser> {
    return this.request('signInWithPassword', {
      email,
      password,
      returnSecureToken: true,
    });
  }

  async updateProfile(params: {
    idToken: string;
    displayName?: string;
    photoUrl?: string;
    deleteAttribute?: Array<'DISPLAY_NAME' | 'PHOTO_URL'>;
  }): Promise<FirebaseUpdateProfileResponse> {
    return this.request('update', {
      idToken: params.idToken,
      displayName: params.displayName,
      photoUrl: params.photoUrl,
      deleteAttribute: params.deleteAttribute,
    });
  }

  async sendPasswordReset(email: string) {
    return this.request('sendOobCode', {
      requestType: 'PASSWORD_RESET',
      email,
    });
  }

  async createAuthRUI(email: string) {
    const response = await fetch(`${this.baseUrl}:createAuthUri?key=${this.apiKey}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        identifier: email,
        continueUri: 'http://localhost:8080',
      }),
    });

    const data = await response.json();
    if (!response.ok) {
      throw new Error(data.error?.message || 'Failed to fetch sign-in methods');
    }
    return data as FirebaseAuthURIResponse;
  }

  async verifyPasswordReset(oobCode: string, newPassword: string) {
    return this.request('resetPassword', {
      oobCode,
      newPassword,
    });
  }

  async refreshToken(refreshToken: string) {
    const response = await fetch(`${this.tokenUrl}?key=${this.apiKey}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        grant_type: 'refresh_token',
        refresh_token: refreshToken,
      }),
    });

    const data = await response.json();
    if (!response.ok) {
      throw new HttpError(data.error?.message || 'Token refresh failed', response.status, data);
    }
    return data as FirebaseRefreshTokenResponse;
  }

  isTokenValid(idToken: string) {
    if (!idToken) {
      return false;
    }
    try {
      const payloadBase64 = idToken.split('.')[1];
      const payloadJson = atob(payloadBase64);
      const payload = JSON.parse(payloadJson);
      const currentTime = Math.floor(Date.now() / 1000);
      return payload.exp > currentTime;
    } catch {
      return false;
    }
  }
}

You can copy the code and initialize the class like this:


// Change the API key here in the following
export const firebaseAuth = new FirebaseAuthCustom("<YOUR_API_KEY>");

Now you can sign up or login the users and obtain tokens from your Chrome extension.

You still would need a way to store the token and make sure the token gets refreshed in time or react on 401 errors.

Please check here all the available apis that Firebase Auth offers:

https://firebase.google.com/docs/reference/rest/auth

Conclusion
#

With this approach, you can integrate Firebase authentication seamlessly into your Chrome extensions without violating Manifest V3 policies. By leveraging Firebase’s REST API, you maintain compliance while ensuring security and performance.

Let me know how this works for you or if you have any questions!

References
#

Firebase Auth REST API

Extension Manifest V3 Requirements