A production-ready Express.js boilerplate with Firebase Authentication and JWT token management.
- 🔐 Firebase Authentication
- 🎫 JWT Token Management
- 🔄 Token Refresh Mechanism
- 🔒 Protected Routes
- 🌐 CORS Configuration
- Node.js (v14 or higher)
- Firebase Account
- npm or yarn
- Clone the repository:
git clone https://github.com/marifdev/express-firebase-auth-boilerplate.git
cd express-firebase-auth-boilerplate- Install dependencies:
npm install- Create a
.envfile in the root directory:
FIREBASE_PROJECT_ID=your_project_id
FIREBASE_CLIENT_EMAIL=your_client_email
FIREBASE_PRIVATE_KEY=your_private_key
JWT_SECRET=your_jwt_secret- Start the server:
npm run devThe system uses three types of tokens:
-
Access Token:
- Valid for 1 day
- Used for API authorization
- Obtained from our backend endpoints
- Include in Authorization header
-
Refresh Token:
- Valid for 7 days
- Used to get new access tokens
- Obtained from our backend endpoints
- Send to refresh-token endpoint
-
Firebase ID Token:
- Generated by Firebase SDK
- Used as fallback authentication
- Obtained from Firebase client SDK
- Automatically managed by Firebase
Sign in using Firebase ID token. First sign in with Firebase client SDK, then call this endpoint.
Client-side code:
// 1. First sign in with Firebase
const userCredential = await firebase.auth().signInWithEmailAndPassword(email, password);
const firebaseIdToken = await userCredential.user.getIdToken();
// 2. Then call your backend
const response = await fetch('/api/signin', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
firebaseIdToken
})
});
const { accessToken, refreshToken } = await response.json();Request:
{
"firebaseIdToken": "firebase.id.token"
}Response:
{
"accessToken": "your.access.token",
"refreshToken": "your.refresh.token",
"user": {
"email": "user@example.com",
"uid": "user123"
}
}Create a new user account. Similar to signin, first create account with Firebase client SDK.
Client-side code:
// 1. First create account with Firebase
const userCredential = await firebase.auth().createUserWithEmailAndPassword(email, password);
const firebaseIdToken = await userCredential.user.getIdToken();
// 2. Then call your backend
const response = await fetch('/api/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
firebaseIdToken
})
});
const { accessToken, refreshToken } = await response.json();Request:
{
"firebaseIdToken": "firebase.id.token"
}Get new tokens using either refresh token or Firebase ID token.
{
"refreshToken": "your.refresh.token",
"firebaseIdToken": "firebase.id.token" // Optional, required if refresh token expired
}Response:
{
"accessToken": "new.access.token",
"refreshToken": "new.refresh.token",
"user": {
"email": "user@example.com",
"uid": "user123"
}
}A simple endpoint to test authentication and token refresh flow.
Headers:
Authorization: Bearer your.access.token
Success Response:
{
"message": "You have accessed a protected endpoint!",
"user": {
"email": "user@example.com",
"uid": "user123"
},
"timestamp": "2024-01-20T15:30:00.000Z"
}Error Response (401 Unauthorized):
{
"error": "Token expired"
}- Client signs up/signs in using Firebase client SDK
- Client gets Firebase ID token
- Client sends Firebase ID token to backend
- Backend verifies Firebase ID token and returns custom tokens (access + refresh)
- Client uses access token for API requests (valid for 1 day)
- When access token expires, use refresh token to get new tokens
- If refresh token expires (after 7 days), use new Firebase ID token to re-authenticate
- Always include access token in Authorization header for protected routes
- Initialize Firebase in your client app:
// Initialize Firebase
firebase.initializeApp({
apiKey: "your-api-key",
authDomain: "your-auth-domain",
projectId: "your-project-id",
// ... other config
});- Handle authentication:
async function signIn(email, password) {
try {
// 1. Sign in with Firebase
const userCredential = await firebase.auth().signInWithEmailAndPassword(email, password);
const firebaseIdToken = await userCredential.user.getIdToken();
// 2. Get custom tokens from your backend
const response = await fetch('/api/signin', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ firebaseIdToken })
});
// 3. Store tokens
const { accessToken, refreshToken } = await response.json();
// Store tokens securely...
return { accessToken, refreshToken };
} catch (error) {
console.error('Sign in failed:', error);
throw error;
}
}- Handle token refresh:
async function refreshTokens(oldRefreshToken) {
try {
// 1. Try with refresh token first
const response = await fetch('/api/refresh-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ refreshToken: oldRefreshToken })
});
if (response.ok) return await response.json();
// 2. If refresh token expired, get new Firebase token
const currentUser = firebase.auth().currentUser;
if (!currentUser) {
throw new Error('No Firebase user found');
}
const firebaseIdToken = await currentUser.getIdToken(true);
// 3. Try with Firebase token
return await fetch('/api/refresh-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
refreshToken: oldRefreshToken,
firebaseIdToken
})
}).then(res => res.json());
} catch (error) {
console.error('Token refresh failed:', error);
throw error;
}
}- Firebase Authentication
- JWT token encryption
- Protected routes
- CORS configuration
- Request validation
The API uses standard HTTP status codes and returns errors in the following format:
{
"error": "Error message here"
}- Fork the repository
- Create your feature branch
- Commit your changes
- Push to the branch
- Create a new Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
For support, open an issue in the repository.