-
Notifications
You must be signed in to change notification settings - Fork 0
Authentication Architecture
This page describes the architecture and authentication flow of the Warnastrophy application, which supports authentication via Google and GitHub.
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.
┌─────────────────────────────────────────────────────────────┐
│ SignInViewModel │
│ - UI State Managment (AuthUIState) │
│ - Orchestration of authentication flows │
└──────────────┬──────────────────────────────┬───────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ AuthRepository │ │ GitHubAuthManager│
│ Firebase │ │ (Singleton) │
└─────────┬────────┘ └────────┬─────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ SignInHelper │ │GitHubOAuthHelper │
│ (Conversion) │ │ (OAuth Flow) │
└──────────────────┘ └──────────────────┘
data class AuthUIState(
val isLoading: Boolean = false,
val user: FirebaseUser? = null,
val errorMsg: String? = null,
val signedOut: Boolean = false
)enum class AuthProvider {
GOOGLE,
GITHUB,
}┌─────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐
│ UI │ │ViewModel │ │Repository│ │ Helper │ │Firebase │
└──┬──┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬────┘
│ │ │ │ │
│ Click │ │ │ │
├──────────>│ │ │ │
│ │ │ │ │
│ │ Set loading │ │ │
│ ├─────────────>│ │ │
│ │ │ │ │
│ │ getCredential│ │ │
│ │ (via CredentialManager) │ │
│ ├──────────────┤ │ │
│ │◄─────────────┤ │ │
│ │ │ │ │
│ │ signInWithGoogle │ │
│ ├─────────────>│ │ │
│ │ │ Extract token│ │
│ │ ├─────────────>│ │
│ │ │◄─────────────┤ │
│ │ │ │ │
│ │ │ Convert to Firebase │
│ │ ├─────────────>│ │
│ │ │◄─────────────┤ │
│ │ │ │ │
│ │ │ signInWithCredential │
│ │ ├──────────────┼─────────────>│
│ │ │◄─────────────┼──────────────┤
│ │ │ │ │
│ │ Success │ │ │
│ │◄─────────────┤ │ │
│ │ │ │ │
│ Update UI │ │ │ │
│◄──────────┤ │ │ │
- Initialization : The user clicks on "Sign in with Google".
-
Configuration : Create
GetSignInWithGoogleOptionwith theserverClientId. -
Credential retrieval: :
- The
CredentialManagerdisplays the system account selection UI. - The user selects its Google account.
- The
-
Token extraction : The
SignInHelperextracts the token ID from the credential. -
Firebase conversion : Create a
GoogleAuthProvider.credential -
Authentication : Call to
FirebaseAuth.signInWithCredential() -
State update : The
viewModelupdates the UI with the authenticated user.
| Exception | Cause | Action |
|---|---|---|
GetCredentialCancellationException |
User canceled | Message "Sign-in cancelled" |
GetCredentialException |
Retrieval failure | Message "Failed to get credentials" |
IllegalStateException |
Invalid credential | Specific error message |
┌────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌────────┐ ┌─────────┐
│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 │ │ │ │ │
│◄────────┤ │ │ │ │
-
PKCE generation :
// Code verifier: 64 random characters val codeVerifier = generateCodeVerifier() // Code challenge : SHA-256(codeVerifier) encoded in base64 val codeChallenge = generateCodeChallenge(codeVerifier)
-
CSRF Protection :
// State: random UUID to prevent CSRF attacks val state = UUID.randomUUID().toString()
-
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 -
Browser opening : Launching an
Intent.ACTION_VIEW
-
Receiving the callback :
GitHubCallbackActivityintercepts the deep link -
Security validation :
// State verification to prevent CSRF validateState(uri) // Verifying that the callback comes from GitHub isGitHubCallback(uri)
-
Extracting the authorization code :
val authCode = uri.getQueryParameter("code")
-
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}
-
Certificate Pinning : Validating GitHub SSL Certificates
CertificatePinner.Builder() .add("github.com", "sha256/uyPYgclc5Jt69vKu92vci6etcBBY5TslweRGEMlMxnc=") .add("github.com", "sha256/e4wu8h9eLNeNUg6cVb5gGWM0PsiM9M3i3E32qKOkBwY=") .build()
-
Parsing the response :
{
"access_token": "gho_xxxxxxxxxxxx",
"token_type": "bearer",
"scope": "user:email"
}- Creation of credentials :
val credentialData = Bundle().apply {
putString("access_token", accessToken)
}
val credential = CustomCredential(
type = CredentialTypes.TYPE_GITHUB,
data = credentialData
)- Notification via callback :
GitHubAuthManager.setCredential(credential)
// Triggers the registered callback in SignInViewModel.init- Firebase conversion :
val firebaseCredential = GithubAuthProvider.getCredential(accessToken)-
Authentification : Call to
FirebaseAuth.signInWithCredential()
| 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" |
| HTTP Code | Exception | Action |
|---|---|---|
| 400-499 | AuthenticationException |
"Authentication failed - invalid credentials" |
| 500-599 | NetworkException |
"GitHub service temporarily unavailable" |
| Timeout | NetworkException |
"Network request failed" |
| Exception | Cause | Message |
|---|---|---|
FirebaseAuthUserCollisionException |
Email already used with another provider | "A user with this email already exists. Please sign in with your other method." |
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()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>
}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): AuthCredentialResponsibilities:
- 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 aboveResponsibilities:
- Managing the OAuth 2.0 flow with PKCE
- Generating and validating security parameters
- Communicating with the GitHub API
- Certificate pinning for security
Security:
-
PKCE (Proof Key for Code Exchange):
- Prevents attacks that intercept authorization codes
- Uses SHA-256 for the challenge code
-
CSRF Protection:
- Randomly generated
stateparameter - Strict validation during callback
- Randomly generated
-
Certificate Pinning:
- Validation of GitHub SSL certificates
- Protection against man-in-the-middle attacks
-
Cleaning sensitive data:
private fun clearSensitiveData() {
codeVerifier?.toCharArray()?.fill(‘*’)
codeVerifier = null
oauthState = null
}-
Firebase configuration:
- Enable Google as a provider in the Firebase Console
- Configure the application's SHA-1 and SHA-256
-
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’-
Client ID:
- Get the
serverClientIdfrom the Google Cloud Console (or fromgoogle-services.jsonfile in the third OAuth client).
- Get the
-
GitHub OAuth application:
- Create an OAuth application on GitHub
- Configure the callback URL:
warnastrophy://github-callback - Obtain
CLIENT_IDandCLIENT_SECRET
-
BuildConfig :
buildConfigField("String", "GITHUB_CLIENT_ID", "\"${gitHubClientId}\"") buildConfigField("String", "GITHUB_CLIENT_SECRET", "\"${gitHubClientSecret}\"")
-
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>
-
Dependencies :
implementation 'com.squareup.okhttp3:okhttp:4.12.0' implementation 'com.google.firebase:firebase-auth:22.3.0'
-
SignInViewModel:
- Test state transitions
- Test error handling
- Test cleanup in
onCleared
-
AuthRepositoryFirebase:
- FirebaseAuth mock
- Test credential conversions
- Test Firebase exception handling
-
GitHubOAuthHelper:
- Test PKCE generation
- Test state validation
- Test response parsing
- Test certificate pinning
-
Full Google flow:
- CredentialManager mock
- Firebase authentication verification
-
Full GitHub flow:
- HTTP request mock
- Deep link callback test
- Firebase authentication verification
-
Never log tokens :
// ❌ BAD Log.d("Auth", "Access token: $accessToken") // ✅ GOOD Log.d("Auth", "Access token obtained successfully")
-
Clean-up sensitive data :
override fun onCleared() { super.onCleared() GitHubAuthManager.clearCallback() }
-
Use only HTTPS :
- All GitHub endpoints use HTTPS
- Certificate pinning enabled
-
Descriptive error messages :
when (e) { is FirebaseAuthUserCollisionException -> "An account with this email already exists" else -> "Authentication error" }
-
Logging errors:
catch (e: Exception) { Log.e("Auth", "Sign-in failed", e) _uiState.update { it.copy(errorMsg = e.localizedMessage) } }
- Avoid synchronous operations :
- Use
suspendfor network operations - Launch in the appropriate dispatcher (
Dispatchers.IO)
- Clean-up resources :
- Cancel coroutines in
onCleared - Release activities references
User → UI → ViewModel → Repository → Firebase
↓
CredentialManager
(System UI)
User → UI → ViewModel → GitHubAuthManager → Browser
↓ ↓
CallbackActivity ← GitHub Authorization
↓
Repository → Firebase