Authentication is one of the most important parts of any application that integrates with external services. While many modern platforms expose OAuth APIs or SDKs for third-party developers, there are situations where an application needs to interact with an authentication flow that is primarily designed for browser users.
Recently, while building a mobile application that integrates with an external football management platform, I needed to design an authentication architecture that was both reliable and efficient. Rather than focusing solely on obtaining an access token, the bigger engineering challenge was ensuring that users were not repeatedly authenticated on every request.
This article explains the architecture I adopted and the design decisions behind it.
The Problem
The mobile application communicates with an ASP.NET Core backend, which in turn communicates with an external service requiring authenticated requests.
A naïve implementation would attempt to authenticate every time a user opens the application. Besides introducing unnecessary latency, this approach would repeatedly perform browser automation and create avoidable load on both the application and the external service.
The objective was therefore to design a solution that:
- authenticates users only when necessary;
- securely stores authentication information;
- reuses valid access tokens;
- refreshes authentication only after token expiry; and
- keeps the mobile client completely unaware of the authentication complexity.
High-Level Architecture
┌───────────────────────┐
│ React Native App │
└──────────┬────────────┘
│
▼
┌───────────────────────┐
│ ASP.NET Core API │
└──────────┬────────────┘
│
▼
┌───────────────────────┐
│ MongoDB Cache │
└──────┬─────────┬──────┘
│ │
Token Exists? │
│ │
▼ ▼
Return Token Launch Playwright
│
▼
External Authentication
│
▼
Receive Access Token
│
▼
Store Token + Expiry
│
▼
Return Authenticated User
Authentication Flow
The authentication pipeline follows a simple decision process.
Step 1 – Check Existing User
When a login request reaches the backend, the application first checks whether the user already exists in MongoDB.
Instead of immediately attempting another login, the stored authentication information is inspected.
Step 2 – Validate Token Expiry
Each stored authentication record contains an access token together with its expiration timestamp.
If the token is still valid, the backend simply reuses it.
This avoids unnecessary authentication while significantly reducing response time for returning users.
Step 3 – Authenticate Only When Necessary
If the user is logging in for the first time, or the existing token has expired, the backend performs a fresh authentication using Playwright.
Once authentication succeeds, the new access token and its expiry time are stored for future requests.
Centralising Authentication
One design decision that proved particularly useful was centralising authentication inside the backend.
The React Native application simply submits user credentials once. After successful authentication, the backend becomes responsible for managing the lifecycle of the access token.
This provides several advantages:
- a simpler mobile application;
- reduced duplication of authentication logic;
- easier maintenance; and
- the flexibility to change authentication behaviour without updating the client application.
Automatic Token Injection
Once a valid token is available, it is attached to the shared HttpClient before any authenticated requests are made.
This means the remainder of the application can focus entirely on business logic without worrying about manually attaching authentication headers for every request.
Why Cache Authentication?
Caching authentication information offers several practical benefits.
Better Performance
Returning users avoid repeating the authentication process, resulting in noticeably faster login times.
Lower Infrastructure Cost
Browser automation is significantly more resource-intensive than validating a stored token. Reusing valid tokens reduces server workload.
Improved User Experience
Users experience a seamless login flow while the backend transparently manages authentication behind the scenes.
Cleaner Architecture
Separating authentication management from business functionality keeps services easier to test and maintain.
Lessons Learned
Designing authentication is about much more than obtaining an access token.
A robust authentication system should answer questions such as:
- When should a user authenticate?
- How long should authentication remain valid?
- When should cached credentials be reused?
- How can authentication complexity be hidden from client applications?
- How can token management remain maintainable as the application grows?
By treating authentication as an architectural concern rather than simply a login feature, it becomes possible to build systems that are faster, cleaner and significantly easier to evolve.
Final Thoughts
Every integration presents its own authentication challenges, but the underlying engineering principles remain consistent.
By introducing token caching, validating token expiry before re-authentication, and centralising authentication management within the backend, I was able to create an architecture that is efficient, maintainable and scalable.
The implementation discussed here was developed while building a mobile integration for a football management platform, but the same design principles can be applied to many applications that interact with external authenticated services.
In future articles, I'll explore additional aspects of the project, including designing reusable API clients, integrating a React Native front end with the backend, and structuring applications for long-term maintainability.
Top comments (0)