Refining Authentication in My Placement Portal: Better Error Handling and Student-Only Login
After implementing email and password authentication for my Placement Portal, I reached an important milestone—the login flow was functional.
Users could enter their credentials, and if they were valid, the backend returned JWT Access and Refresh Tokens. Otherwise, the request failed.
Technically, it worked.
From a product perspective, however, it still had two major shortcomings.
- The login process wasn't informative enough when authentication failed.
- It allowed every valid user to access the frontend, even though only students should log in through the portal. Administrators, universities, and companies use the Django Admin interface instead.
Instead of considering the feature complete, I decided to refine the authentication flow.
Making Login Failures More Informative
Authentication doesn't always end in success or failure. From the user's perspective, there are several possible outcomes.
I identified three scenarios:
- Successful authentication
- Invalid credentials or validation errors
- Unexpected errors, such as request timeouts or server failures
Each scenario deserves a different response.
Rather than displaying a generic "Login Failed" message, I wanted users to understand exactly what happened.
Separating Frontend and Backend Responsibilities
To make the login flow cleaner and more secure, I split it into two layers.
1. Next.js Client
The login page is responsible only for:
- Collecting user credentials
- Displaying loading indicators
- Showing success and error notifications
- Redirecting authenticated users
2. Next.js Internal API Route
Instead of allowing the browser to communicate directly with the Django backend, I introduced an internal API route.
This route:
- Validates incoming requests
- Hides the actual Django backend URL
- Forwards requests to the backend
- Returns responses to the frontend
This creates a cleaner separation between the presentation layer and the backend API while avoiding direct exposure of backend endpoints.
Handling Authentication Responses
Once the Django backend receives valid credentials, it returns JWT tokens as HTTP-only cookies.
Because the server sets these cookies automatically, the frontend never needs to manually store authentication tokens.
The client then handles the response accordingly.
On Success
- Display a success notification
- Accept the HTTP-only cookies
- Redirect the user to the dashboard
On Failure
The frontend extracts the error returned by the backend and displays it as a toast notification.
Instead of showing a vague error message, users receive meaningful feedback about why authentication failed.
Small improvements like this significantly improve the overall user experience.
Restricting Login to Students
The next challenge wasn't authentication—it was authorization.
Although every user could authenticate successfully, only students should access the frontend portal.
Companies, universities, and administrators interact with the system through Django Admin.
To enforce this rule, I customized dj-rest-auth by overriding the UserDetailSerializer.
I added a new field:
role
Now, every successful authentication also returns the authenticated user's role.
Before completing the login process, the frontend checks that role.
If the authenticated user is a Student:
- Login continues normally.
- The user is redirected to the dashboard.
If the authenticated user belongs to any other role:
- Authentication is rejected.
- An informative error message is displayed.
- No access to the frontend is granted.
This ensures that every user logs into the correct interface while keeping the authentication flow simple and secure.
Lessons Learned
One of the biggest lessons from building this feature was that a working login page isn't necessarily a good login experience.
Authentication should not only verify identity—it should also:
- Provide meaningful feedback
- Protect backend infrastructure
- Restrict access based on business rules
- Keep sensitive tokens secure
Small architectural decisions like introducing internal API routes and role-based restrictions make the application easier to maintain as it grows.
What's Next?
With authentication and student-only login now complete, my next milestone is implementing Role-Based Access Control (RBAC) across the Placement Portal so every API endpoint enforces permissions based on the authenticated user's role.
I'm documenting every major architectural decision while building this Placement Portal in public.