Skip to content

Authentication Architecture

NailLaraqui edited this page Dec 16, 2025 · 5 revisions

This page describes the architecture and authentication flow of the Warnastrophy application, which supports authentication via Google and GitHub.

Overview

The application uses Firebase Authentication as its authentication backend, with two OAuth providers:

  • Google Sign-in: Via Android's Credential Manager API.
  • GitHub OAuth: Via a custom OAuth 2.0 flow with PKCE.

Component Architecture

Main components

┌─────────────────────────────────────────────────────────────┐
│                      SignInViewModel                        │
│  - UI State Managment (AuthUIState)                         │
│  - Orchestration of authentication flows                    │
└──────────────┬──────────────────────────────┬───────────────┘
               │                              │
               ▼                              ▼
    ┌──────────────────┐           ┌──────────────────┐
    │ AuthRepository   │           │ GitHubAuthManager│
    │ Firebase         │           │  (Singleton)     │
    └─────────┬────────┘           └────────┬─────────┘
              │                             │
              ▼                             ▼
    ┌──────────────────┐           ┌──────────────────┐
    │  SignInHelper    │           │GitHubOAuthHelper │
    │ (Conversion)     │           │  (OAuth Flow)    │
    └──────────────────┘           └──────────────────┘

Data models

AuthUIState

data class AuthUIState(
  val isLoading: Boolean = false,
  val user: FirebaseUser? = null,
  val errorMsg: String? = null,
  val signedOut: Boolean = false
)

AuthProvider

enum class AuthProvider {
  GOOGLE,
  GITHUB,
}

Google authentication flow

Sequence diagram

┌─────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐   ┌─────────┐
│ UI  │   │ViewModel │   │Repository│   │  Helper  │   │Firebase │
└──┬──┘   └────┬─────┘   └────┬─────┘   └────┬─────┘   └────┬────┘
   │           │              │              │              │
   │ Click     │              │              │              │
   ├──────────>│              │              │              │
   │           │              │              │              │
   │           │ Set loading  │              │              │
   │           ├─────────────>│              │              │
   │           │              │              │              │
   │           │ getCredential│              │              │
   │           │ (via CredentialManager)     │              │
   │           ├──────────────┤              │              │
   │           │◄─────────────┤              │              │
   │           │              │              │              │
   │           │ signInWithGoogle            │              │
   │           ├─────────────>│              │              │
   │           │              │ Extract token│              │
   │           │              ├─────────────>│              │
   │           │              │◄─────────────┤              │
   │           │              │              │              │
   │           │              │ Convert to Firebase         │
   │           │              ├─────────────>│              │
   │           │              │◄─────────────┤              │
   │           │              │              │              │
   │           │              │ signInWithCredential        │
   │           │              ├──────────────┼─────────────>│
   │           │              │◄─────────────┼──────────────┤
   │           │              │              │              │
   │           │ Success      │              │              │
   │           │◄─────────────┤              │              │
   │           │              │              │              │
   │ Update UI │              │              │              │
   │◄──────────┤              │              │              │

Detailed steps

  1. Initialization : The user clicks on "Sign in with Google".
  2. Configuration : Create GetSignInWithGoogleOption with the serverClientId.
  3. Credential retrieval: :
    • The CredentialManager displays the system account selection UI.
    • The user selects its Google account.
  4. Token extraction : The SignInHelper extracts the token ID from the credential.
  5. Firebase conversion : Create a GoogleAuthProvider.credential
  6. Authentication : Call to FirebaseAuth.signInWithCredential()
  7. State update : The viewModel updates the UI with the authenticated user.

Error handling

Exception Cause Action
GetCredentialCancellationException User canceled Message "Sign-in cancelled"
GetCredentialException Retrieval failure Message "Failed to get credentials"
IllegalStateException Invalid credential Specific error message

GitHub authentication flow

Sequence diagram

┌────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌────────┐ ┌─────────┐
│UI  │ │ViewModel │ │GitHubAuth │ │Callback  │ │ Helper │ │Firebase │
│    │ │          │ │ Manager   │ │ Activity │ │        │ │         │
└─┬──┘ └────┬─────┘ └─────┬─────┘ └────┬─────┘ └───┬────┘ └────┬────┘
  │         │             │            │           │           │
  │ Click   │             │            │           │           │
  ├────────>│             │            │           │           │
  │         │             │            │           │           │
  │         │ startGitHubSignIn        │           │           │
  │         ├────────────>│            │           │           │
  │         │             │            │           │           │
  │         │             │ Generate PKCE          │           │
  │         │             │            │           │           │
  │         │             │ Open browser           │           │
  │         │             ├────────────┼──────────>│           │
  │         │             │            │           │           │
  │         │             │ [User authorizes on GitHub.com]    │
  │         │             │            │           │           │
  │         │             │ Redirect   │           │           │
  │         │             │◄───────────┤           │           │
  │         │             │            │           │           │
  │         │             │ onCreate   │           │           │
  │         │             ├───────────>│           │           │
  │         │             │            │           │           │
  │         │             │            │ Validate state        │
  │         │             │            ├──────────>│           │
  │         │             │            │           │           │
  │         │             │            │ Exchange code         │
  │         │             │            ├──────────>│           │
  │         │             │            │◄──────────┤           │
  │         │             │            │ access_token          │
  │         │             │            │           │           │
  │         │             │ setCredential          │           │
  │         │             │◄───────────┤           │           │
  │         │             │            │           │           │
  │         │ performSignIn            │           │           │
  │         │◄────────────┤            │           │           │
  │         │             │            │           │           │
  │         │ signInWithGithub         │           │           │
  │         ├─────────────────────────────────────────────────>│
  │         │◄─────────────────────────────────────────────────┤
  │         │             │            │           │           │
  │ Update  │             │            │           │           │
  │◄────────┤             │            │           │           │

Detailed steps

Phase 1 : OAuth initialization

  1. PKCE generation :

    // Code verifier: 64 random characters
    val codeVerifier = generateCodeVerifier()
    
    // Code challenge : SHA-256(codeVerifier) encoded in base64
    val codeChallenge = generateCodeChallenge(codeVerifier)
  2. CSRF Protection :

    // State: random UUID to prevent CSRF attacks
    val state = UUID.randomUUID().toString()
  3. Authorization URL construction :

    https://github.com/login/oauth/authorize?
      client_id={CLIENT_ID}&
      redirect_uri=warnastrophy://github-callback&
      scope=user:email&
      state={STATE}&
      code_challenge={CODE_CHALLENGE}&
      code_challenge_method=S256
    
  4. Browser opening : Launching an Intent.ACTION_VIEW

Phase 2 : GitHub Callback

  1. Receiving the callback : GitHubCallbackActivity intercepts the deep link

  2. Security validation :

    // State verification to prevent CSRF
    validateState(uri)
    
    // Verifying that the callback comes from GitHub
    isGitHubCallback(uri)
  3. Extracting the authorization code :

    val authCode = uri.getQueryParameter("code")

Phase 3 : Token exchange

  1. POST request to GitHub :

    POST https://github.com/login/oauth/access_token
    Content-Type: application/x-www-form-urlencoded
    
    client_id={CLIENT_ID}&
    client_secret={CLIENT_SECRET}&
    code={AUTH_CODE}&
    redirect_uri=warnastrophy://github-callback&
    code_verifier={CODE_VERIFIER}
  2. Certificate Pinning : Validating GitHub SSL Certificates

    CertificatePinner.Builder()
       .add("github.com", "sha256/uyPYgclc5Jt69vKu92vci6etcBBY5TslweRGEMlMxnc=")
       .add("github.com", "sha256/e4wu8h9eLNeNUg6cVb5gGWM0PsiM9M3i3E32qKOkBwY=")
       .build()
  3. Parsing the response :

{
  "access_token": "gho_xxxxxxxxxxxx",
  "token_type": "bearer",
  "scope": "user:email"
}

Phase 4 : Firebase Authentication

  1. Creation of credentials :
val credentialData = Bundle().apply {
  putString("access_token", accessToken)
}
val credential = CustomCredential(
  type = CredentialTypes.TYPE_GITHUB,
  data = credentialData
)
  1. Notification via callback :
GitHubAuthManager.setCredential(credential)
// Triggers the registered callback in SignInViewModel.init
  1. Firebase conversion :
val firebaseCredential = GithubAuthProvider.getCredential(accessToken)
  1. Authentification : Call to FirebaseAuth.signInWithCredential()

Error handling

OAuth errors

Error code Description User message
access_denied User denied access "You denied access to your GitHub account"
unauthorized_client Unauthorized application "Application is not authorized"
invalid_scope Invalid scope "Invalid permissions requested"
server_error GitHub server error "GitHub server error, please try again"

Network errors

HTTP Code Exception Action
400-499 AuthenticationException "Authentication failed - invalid credentials"
500-599 NetworkException "GitHub service temporarily unavailable"
Timeout NetworkException "Network request failed"

Firebase errors

Exception Cause Message
FirebaseAuthUserCollisionException Email already used with another provider "A user with this email already exists. Please sign in with your other method."

Detailed components

SignInViewModel

Responsibilities :

  • Managing the UI state via StateFlow<AuthUIState>
  • Orchestration of authentication flows
  • Error handling and loading statuses management
  • Cleaning resources (onCleared)

Main methods :

// Google Authentication
fun signInWithGoogle(
    context: Context,
    credentialManager: CredentialManager,
    serverClientId: String
)

// GitHub Authentication
fun startGitHubSignIn(activity: Activity, scope: String = "user:email")

// GitHub Cancellation
fun onGitHubSignInCancelled()

// Logout
fun signOut()

// Error handling
fun clearErrorMsg()

AuthRepositoryFirebase

Responsibilities:

  • Interface with Firebase Authentication
  • Convert OAuth credentials to Firebase credentials
  • Manage authentication results

Interface :

interface AuthRepository {
    suspend fun signIn(
        credential: Credential,
        authProvider: AuthProvider
    ): Result<FirebaseUser>
    
    fun signOut(): Result<Unit>
}

SignInHelper

Reponsibilities :

  • Token extraction from the Bundle
  • Converting tokens to Firebase credentials

Methods :

fun extractGoogleIdTokenCredential(bundle: Bundle): GoogleIdTokenCredential
fun extractAccessToken(bundle: Bundle): String
fun googleToFirebaseCredential(idToken: String): AuthCredential
fun githubToFirebaseCredential(accessToken: String): AuthCredential

GitHubAuthManager

Responsibilities:

  • Singleton to manage the GitHub OAuth lifecycle
  • Temporary storage of credentials
  • Callback system to notify the ViewModel

Callback mechanism :

// In SignInViewModel.init
GitHubAuthManager.onCredentialReady { credential ->
    viewModelScope.launch {
        performSignIn(credential, AuthProvider.GITHUB)
    }
}

// In GitHubCallbackActivity
GitHubAuthManager.setCredential(credential)
// Automatically triggers the callback above

GitHubOAuthHelper

Responsibilities:

  • Managing the OAuth 2.0 flow with PKCE
  • Generating and validating security parameters
  • Communicating with the GitHub API
  • Certificate pinning for security

Security:

  1. PKCE (Proof Key for Code Exchange):

    • Prevents attacks that intercept authorization codes
    • Uses SHA-256 for the challenge code
  2. CSRF Protection:

    • Randomly generated state parameter
    • Strict validation during callback
  3. Certificate Pinning:

    • Validation of GitHub SSL certificates
    • Protection against man-in-the-middle attacks
  4. Cleaning sensitive data:

private fun clearSensitiveData() {
    codeVerifier?.toCharArray()?.fill(‘*’)
    codeVerifier = null
    oauthState = null
}

Required configuration

Google Sign-In

  1. Firebase configuration:

    • Enable Google as a provider in the Firebase Console
    • Configure the application's SHA-1 and SHA-256
  2. Dependencies:

   implementation 'androidx.credentials:credentials:1.2.0'
   implementation ‘com.google.android.libraries.identity.googleid:googleid:1.1.0’
   implementation ‘com.google.firebase:firebase-auth:22.3.0’
  1. Client ID:
    • Get the serverClientId from the Google Cloud Console (or from google-services.json file in the third OAuth client).

GitHub OAuth

  1. GitHub OAuth application:

    • Create an OAuth application on GitHub
    • Configure the callback URL: warnastrophy://github-callback
    • Obtain CLIENT_ID and CLIENT_SECRET
  2. BuildConfig :

    buildConfigField("String", "GITHUB_CLIENT_ID", "\"${gitHubClientId}\"")
    buildConfigField("String", "GITHUB_CLIENT_SECRET", "\"${gitHubClientSecret}\"")
  3. AndroidManifest.xml :

    <activity
        android:name=".core.auth.GitHubCallbackActivity"
        android:exported="true"
        android:launchMode="singleTask">
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data
                android:scheme="warnastrophy"
                android:host="github-callback" />
        </intent-filter>
    </activity>
  4. Dependencies :

    implementation 'com.squareup.okhttp3:okhttp:4.12.0'
    implementation 'com.google.firebase:firebase-auth:22.3.0'

Tests

Recommended unit tests

  1. SignInViewModel:

    • Test state transitions
    • Test error handling
    • Test cleanup in onCleared
  2. AuthRepositoryFirebase:

    • FirebaseAuth mock
    • Test credential conversions
    • Test Firebase exception handling
  3. GitHubOAuthHelper:

    • Test PKCE generation
    • Test state validation
    • Test response parsing
    • Test certificate pinning

Integration tests

  1. Full Google flow:

    • CredentialManager mock
    • Firebase authentication verification
  2. Full GitHub flow:

    • HTTP request mock
    • Deep link callback test
    • Firebase authentication verification

Best practices

Security

  1. Never log tokens :

    // ❌ BAD
    Log.d("Auth", "Access token: $accessToken")
    
    // ✅ GOOD
    Log.d("Auth", "Access token obtained successfully")
  2. Clean-up sensitive data :

    override fun onCleared() {
        super.onCleared()
        GitHubAuthManager.clearCallback()
    }
  3. Use only HTTPS :

    • All GitHub endpoints use HTTPS
    • Certificate pinning enabled

Error handling

  1. Descriptive error messages :

    when (e) {
        is FirebaseAuthUserCollisionException -> 
            "An account with this email already exists"
        else -> 
            "Authentication error"
    }
  2. Logging errors:

    catch (e: Exception) {
        Log.e("Auth", "Sign-in failed", e)
        _uiState.update { 
            it.copy(errorMsg = e.localizedMessage) 
        }
    }

Performance

  1. Avoid synchronous operations :
  • Use suspend for network operations
  • Launch in the appropriate dispatcher (Dispatchers.IO)
  1. Clean-up resources :
  • Cancel coroutines in onCleared
  • Release activities references

Simplified diagrams

Google overview

User → UI → ViewModel → Repository → Firebase
                ↓
         CredentialManager
         (System UI)

GitHub overview

User → UI → ViewModel → GitHubAuthManager → Browser
                ↓                              ↓
         CallbackActivity ← GitHub Authorization
                ↓
         Repository → Firebase

Resources

Clone this wiki locally