Synergising Mobile Security
In the bustling kitchen of software engineering, product and development teams are like chefs and sous-chefs, each feeding into the other’s culinary creations. Today, we’re cooking up a storm in mobile authentication, exploring how thick mobile applications communicate securely with backend APIs. Grab your aprons—let’s dive into the recipe!
The Appetiser: authenticating the user. A simple and secure approach here is OpenID and OAuth, leveraging trusted service providers like Google Auth. In a Flutter mobile app, integrating Firebase Authentication with Google Sign-In is as smooth as butter on toast.
Here’s how you whip it up in dart:
// Import the necessary packages
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
Future<UserCredential> signInWithGoogle() async {
// Trigger the Google Sign-In flow
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
// Obtain the auth details from the request
final GoogleSignInAuthentication googleAuth = await googleUser!.authentication;
// Create a new credential
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
// Sign in to Firebase with the Google user credentials
return await FirebaseAuth.instance.signInWithCredential(credential);
}
Once authenticated, you have access to the user’s context—like their email—based on the privileges granted in Firebase. It’s like getting the secret family recipe!
The Main Course: Now, onto the backend (BE) API. We need to ensure that our server trusts the user’s identity without making them log in again. Enter the bearer token, specifically the idToken we obtained earlier.
In our Python backend using Firebase Admin SDK, we can verify this token:
from firebase_admin import auth
def verify_id_token(id_token):
try:
# Verify the token and decode it
decoded_token = auth.verify_id_token(id_token)
uid = decoded_token['uid']
# Token is valid and not expired
return uid
except auth.ExpiredIdTokenError:
# Token has expired
return None
except auth.InvalidIdTokenError:
# Token is invalid
return None
This method ensures secure communication between the mobile app and the backend, all without any secret sauces—no additional auth keys or embedded secrets in the app. It’s the equivalent of a secure kitchen pass-through window; orders (API calls) go in, meals (data) come out, and only authorized staff have access.
The Dessert Dilemma: But wait—what happens when the app needs to make API calls while in the background? Think of periodic activities like refreshing data or syncing, orchestrated by WorkManager or AlarmManager. Since the user isn’t actively engaging with the app, re-authentication isn’t straightforward. Our bearer token might have expired, leaving our background tasks out in the cold.
Here’s the clever bit: we can cache the idToken, which is typically valid for an hour. On the backend, we adjust our token verification to allow a grace period while still ensuring the token’s integrity by verifying its JWT signature.
An idToken is a JSON Web Token (JWT), which consists of three parts:
1. Header: Contains metadata about the token, such as the signing algorithm and type.
2. Payload: Contains the claims or user data (e.g., uid, email).
领英推荐
3. Signature: A cryptographic signature created using the header and payload, signed with Google’s private key.
Even if the token is expired, the signature remains valid for verifying the token’s integrity and authenticity. By verifying the signature against Google’s public keys, we can confirm that the token was indeed issued by Google and hasn’t been tampered with.
Here’s how we modify our verification function to include signature verification and a grace period:
from firebase_admin import auth
from datetime import datetime, timedelta
import jwt
import requests
def verify_id_token_with_grace_period(id_token):
try:
# Attempt to verify the token normally
decoded_token = auth.verify_id_token(id_token)
return decoded_token['uid']
except auth.ExpiredIdTokenError:
# Token has expired; proceed to verify signature manually
# Fetch Google's public keys
response = requests.get('https://www.googleapis.com/robot/v1/metadata/x509/[email protected]')
public_keys = response.json()
# Get the key ID from the token header
unverified_header = jwt.get_unverified_header(id_token)
key_id = unverified_header['kid']
public_key_pem = public_keys.get(key_id)
if public_key_pem:
# Decode the token without verifying expiration
decoded_token = jwt.decode(
id_token,
public_key_pem,
algorithms=['RS256'],
audience='YOUR_FIREBASE_PROJECT_ID',
options={'verify_exp': False}
)
# Manually check the expiration time
exp = decoded_token.get('exp')
expired_at = datetime.utcfromtimestamp(exp)
if datetime.utcnow() - expired_at < timedelta(days=7):
# Token expired within the acceptable grace period
return decoded_token['uid']
else:
# Token expired too long ago
return None
else:
# Public key not found; token is invalid
return None
except Exception as e:
# Other errors
return None
Verifying the JWT signature is crucial because:
? Authenticity: Confirms the token was issued by a trusted authority (Google).
? Integrity: Ensures the token hasn’t been tampered with.
? User Identity: Validates the claims inside the token, like the user’s UID.
Even if the token has expired, the signature verification guarantees that the token is legitimate. This allows us to trust the user’s identity during the grace period without compromising security.
The Chef’s Special: Now, here’s where the magic happens. This technical workaround requires a keen eye on security posture and risk mitigation. But to truly optimize, the development team needs to feed back into the product strategy.
If a user hasn’t opened the app in two weeks, perhaps we need to spice things up. Maybe it’s time for a push notification with a tempting offer or new feature announcement—something to entice the user back into the app, refreshing their token in the process.
By aligning development constraints with product incentives, we avoid implementing insecure shortcuts or adding unnecessary complexity. Instead, we create a harmonious cycle where user engagement naturally keeps the system secure and functional.
In the end, secure mobile authentication isn’t just about implementing the right protocols—it’s about the symbiotic relationship between product vision and technical execution. By understanding both the kitchen and the dining room, we serve up a dish that’s not only secure and efficient but also delightful for the user.
Bon appétit!
Tech Strategist | Engineering Leader | Startup Advisor | IIT Bombay
3 个月Abhisek Datta You may find this interesting.
Tech Strategist | Engineering Leader | Startup Advisor | IIT Bombay
3 个月Incentivizing product to solve dev/security constraints! Never thought of it this way.
Digital Identity & adv. Cryptography - PHE & FHE(MS SEAL), AGI - RAG, Vector Database, Knowledge Graph, Langchain, Langgraph and Agent Frameworks, Advanced Python, OpenCV, Spring-Boot Microservices Enterprise Application
3 个月Very helpful and interesting!