Skip to main content

Overview

Security is a critical aspect of NASAExplorer. This guide covers best practices for protecting sensitive credentials, implementing Firebase security rules, and maintaining a secure development workflow.

Sensitive Files Management

NASAExplorer keeps sensitive configuration files out of version control to prevent credential exposure.

Protected Files

Never commit these files to version control:
  • local.properties - Contains NASA API key
  • google-services.json - Contains Firebase configuration
  • Any .env files with credentials
  • keystore files for app signing

.gitignore Configuration

The project’s .gitignore file includes:
.gitignore
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
Notice that local.properties is listed twice for redundancy. This ensures it’s never accidentally committed.

API Key Security

NASA API Key Protection

The NASA API key is stored securely using BuildConfig:

1. Storage in local.properties

local.properties
nasaApiKey=YOUR_NASA_API_KEY_HERE

2. Injection at Build Time

app/build.gradle.kts
// Read local.properties
val localProperties = Properties().apply {
    load(project.rootProject.file("local.properties").inputStream())
}

// Extract API key
val nasaApiKey: String = localProperties.getProperty("nasaApiKey") ?: ""

android {
    defaultConfig {
        // Inject as BuildConfig field (not visible in source code)
        buildConfigField("String", "NASA_API_KEY", "\"$nasaApiKey\"")
    }
    
    buildFeatures {
        buildConfig = true
    }
}

3. Access via BuildConfig

NasaRepository.kt
class NasaRepository @Inject constructor(
    private val nasaApiService: NasaApiService
) {
    companion object {
        private const val API_KEY = BuildConfig.NASA_API_KEY
    }

    suspend fun getImageOfTheDay(date: String? = null): NasaModel {
        val response = nasaApiService.getImageOfTheDay(
            apiKey = API_KEY,
            date = date
        )
        return response.toNasaModel()
    }
}

Why This Approach?

1

Build-Time Injection

The API key is injected during compilation, not stored in source code.
2

ProGuard Protection

In release builds, ProGuard obfuscates the BuildConfig class, making reverse engineering more difficult.
3

No Source Code Exposure

The key never appears in .kt files that get committed to Git.
Limitation: BuildConfig fields can still be extracted from APK files through reverse engineering. For production apps with strict security requirements, consider using a backend API proxy instead.

Firebase Security

Authentication Security

The AuthService implements secure authentication patterns:
AuthService.kt
class AuthService @Inject constructor(
    private val firebaseAuth: FirebaseAuth
) {

    suspend fun login(email: String, password: String): FirebaseUser? {
        return firebaseAuth
            .signInWithEmailAndPassword(email, password)
            .await()
            .user
    }

    suspend fun register(email: String, password: String): FirebaseUser? {
        return firebaseAuth
            .createUserWithEmailAndPassword(email, password)
            .await()
            .user
    }

    fun userLogout() {
        firebaseAuth.signOut()
    }

    fun isUserLogged(): Boolean {
        return getCurrentUser() != null
    }

    private fun getCurrentUser() = firebaseAuth.currentUser
}

Best Practices Implemented

  1. Coroutine-based async operations - No blocking calls
  2. Null safety - Proper handling of nullable FirebaseUser
  3. Error propagation - Exceptions bubble up for proper handling
  4. Session management - Clean logout functionality

Firebase Realtime Database Rules

Development Rules (Current)

For development, the database uses test mode rules:
{
  "rules": {
    ".read": true,
    ".write": true
  }
}
Test mode allows unrestricted access. This is acceptable for development but must be changed before production deployment.
For production, implement user-specific access control:
firebase-rules.json
{
  "rules": {
    "favorites": {
      "$uid": {
        ".read": "auth != null && auth.uid == $uid",
        ".write": "auth != null && auth.uid == $uid",
        ".validate": "newData.hasChildren(['id', 'title', 'url'])",
        "$favoriteId": {
          ".validate": "newData.child('id').isString() && newData.child('title').isString() && newData.child('url').isString()"
        }
      }
    },
    "comments": {
      "$uid": {
        ".read": "auth != null && auth.uid == $uid",
        ".write": "auth != null && auth.uid == $uid",
        "$favoriteId": {
          ".validate": "newData.isString() && newData.val().length <= 1000"
        }
      }
    }
  }
}

Rule Breakdown

Favorites Rules

"favorites": {
  "$uid": {
    ".read": "auth != null && auth.uid == $uid",
    ".write": "auth != null && auth.uid == $uid"
  }
}
  • User isolation: Each user can only access their own favorites
  • Authentication required: auth != null ensures user is logged in
  • UID matching: auth.uid == $uid verifies user owns the data

Comments Rules

"comments": {
  "$uid": {
    "$favoriteId": {
      ".validate": "newData.isString() && newData.val().length <= 1000"
    }
  }
}
  • Type validation: Ensures comments are strings
  • Length limit: Prevents abuse with 1000 character max
  • User-scoped: Comments tied to user’s favorites

Applying Security Rules

1

Access Firebase Console

Navigate to Firebase Console and select your project.
2

Open Realtime Database Rules

  1. Go to Realtime Database section
  2. Click on the Rules tab
3

Update Rules

Replace the existing rules with the production rules provided above.
4

Publish Changes

Click Publish to activate the new security rules.
5

Test Your App

Verify that:
  • Users can only access their own favorites
  • Unauthenticated requests are denied
  • Data validation works correctly
After applying production rules, test thoroughly. Users will be unable to access data if authentication fails or rules are misconfigured.

Code-Level Security

User Authentication Checks

All database operations verify user authentication:
FavoritesViewModel.kt
fun loadFavoriteImages() {
    val userId = firebaseAuth.currentUser?.uid
    if (userId != null) {
        viewModelScope.launch {
            try {
                val favoriteRef = firebaseDatabase.reference
                    .child("favorites")
                    .child(userId)
                val snapshot = favoriteRef.get().await()
                // ... process data
            } catch (e: Exception) {
                _errorMessage.value = "Error al mostrar la lista de favoritos"
            }
        }
    } else {
        _errorMessage.value = "Usuario no autenticado"
    }
}

Key Security Patterns

  1. Null-safe UID extraction
    val userId = firebaseAuth.currentUser?.uid
    if (userId != null) { /* proceed */ }
    
  2. User-scoped references
    firebaseDatabase.reference.child("favorites").child(userId)
    
  3. Error handling
    try {
        // Database operation
    } catch (e: Exception) {
        _errorMessage.value = "Error message"
    }
    
  4. Coroutine safety
    viewModelScope.launch {
        // Async operation with lifecycle awareness
    }
    

Network Security

HTTPS Enforcement

All network communication uses HTTPS:
ApiNetworkModule.kt
@Module
@InstallIn(SingletonComponent::class)
object ApiNetworkModule {
    private const val BASE_URL = "https://api.nasa.gov/"  // HTTPS

    @Singleton
    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .client(okHttpClient)
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}
Firebase automatically uses HTTPS:
https://nasaexplorer-f002d-default-rtdb.europe-west1.firebasedatabase.app

Network Security Config (Optional)

For additional security, you can add a network security configuration:
res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
    <domain-config cleartextTrafficPermitted="false">
        <domain includeSubdomains="true">api.nasa.gov</domain>
        <domain includeSubdomains="true">firebase.google.com</domain>
    </domain-config>
</network-security-config>
Reference in AndroidManifest.xml:
<application
    android:networkSecurityConfig="@xml/network_security_config"
    ...>
</application>

ProGuard/R8 Configuration

For release builds, enable code obfuscation:
app/build.gradle.kts
android {
    buildTypes {
        release {
            isMinifyEnabled = true  // Enable code shrinking
            isShrinkResources = true  // Remove unused resources
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

ProGuard Rules for Firebase

proguard-rules.pro
# Firebase
-keepattributes Signature
-keepattributes *Annotation*
-keepattributes EnclosingMethod
-keepattributes InnerClasses

# Gson (used by Retrofit)
-keepattributes Signature
-keep class com.ccandeladev.nasaexplorer.data.api.** { *; }
-keep class com.ccandeladev.nasaexplorer.domain.** { *; }

# Firebase Realtime Database
-keepclassmembers class com.ccandeladev.nasaexplorer.domain.** {
    *;
}

Security Checklist

1

Credential Protection

  • local.properties in .gitignore
  • google-services.json excluded from version control
  • No hardcoded API keys in source code
  • BuildConfig used for API key access
2

Firebase Security

  • Email/password authentication enabled
  • Production database rules implemented
  • User authentication verified before database access
  • User-scoped data references used
3

Network Security

  • HTTPS enforced for all API calls
  • Firebase connections use secure URLs
  • Network security config implemented (optional)
  • Certificate pinning considered for sensitive apps
4

Build Security

  • ProGuard enabled for release builds
  • Code obfuscation active
  • Resource shrinking enabled
  • Signed APK with secure keystore
5

Testing

  • Security rules tested thoroughly
  • Unauthorized access attempts blocked
  • Error messages don’t leak sensitive info
  • Authentication flows working correctly

Incident Response

If API Key is Compromised

1

Rotate Key Immediately

  1. Go to NASA API Portal
  2. Generate a new API key
  3. Update local.properties with new key
  4. Invalidate the old key if possible
2

Review Access Logs

Check if the compromised key was abused (rate limit exceeded, unusual usage patterns).
3

Audit Repository

Search Git history to ensure the key was never committed:
git log -p | grep "nasaApiKey"

If Firebase Config is Exposed

1

Assess Risk

Determine what was exposed (public keys vs. sensitive credentials).
2

Update Security Rules

Immediately apply strict production security rules if not already done.
3

Rotate Credentials

If sensitive credentials were exposed:
  1. Create a new Firebase project
  2. Migrate users and data
  3. Update app configuration
  4. Delete compromised project
4

Monitor Activity

Check Firebase Console for:
  • Unusual authentication attempts
  • Unexpected database reads/writes
  • Abnormal traffic patterns

Additional Security Resources

Next Steps

With security configured:
  1. Review Firebase configuration
  2. Learn about testing strategies
  3. Review building and deployment
Security is an ongoing process. Regularly review and update your security measures as new vulnerabilities and best practices emerge.

Build docs developers (and LLMs) love